[1. 相关资源]
-
【知乎 - Vue Function-based API RFC】Vue Function-based API RFC - 知乎
-
【github - vuejs/composition-api】https://github.com/vuejs/composition-api
-
【github - composition-api/CHANGELOG.md】https://github.com/vuejs/composition-api/blob/master/CHANGELOG.md
-
【开源中国 - 尤雨溪公布 Vue 3.0 开发路线:将从头开始重写 3.0】尤雨溪公布 Vue 3.0 开发路线:将从头开始重写 3.0 - OSCHINA - 中文开源技术交流社区
[2. 初始化项目]
-
安装 vue-cli3
npm install -g @vue/cli # OR yarn global add @vue/cli
[3. setup]
setup() 函数是 vue3 中,专门为组件提供的新属性。它为我们使用 vue3 的 Composition API 新特性提供了统一的入口。
1、setup 函数时,它将接受两个参数:(props、context(包含attrs、slots、emit))
2、setup函数是处于 生命周期函数 beforeCreate 和 Created 两个钩子函数之前的函数
3、执行 setup 时,组件实例尚未被创建(在 setup() 内部,this 不会是该活跃实例的引用,即不指向vue实例,Vue 为了避免我们错误的使用,直接将 setup函数中的this修改成了 undefined)
4、与模板一起使用:需要返回一个对象 (在setup函数中定义的变量和方法最后都是需要 return 出去的 不然无法再模板中使用)
5、使用渲染函数:可以返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态
注意事项:
1、setup函数中不能使用this。Vue 为了避免我们错误的使用,直接将 setup函数中的this修改成了 undefined)
2、setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新。但是,因为 props 是响应式的,你不能使用 ES6 解构,因为它会消除 prop 的响应性。
如果需要解构 prop,可以通过使用 setup 函数中的toRefs 来完成此操作: 父传子,props
import { toRefs } from 'vue'
setup(props) { const { title } = toRefs(props)
console.log(title.value)
onMounted(() => {
console.log('title: ' + props.title)
})
}
子传父,事件 - Emitting Events
举例,现在我们想在点击提交按钮时触发一个login的事件。
在 Vue2 中我们会调用到this.$emit然后传入事件名和参数对象。
login () { this.$emit('login', { username: this.username, password: this.password }) }
在setup()中的第二个参数content对象中就有emit,这个是和this.$emit是一样的。那么我们只要在setup()接收第二个参数中使用分解对象法取出emit就可以在setup方法中随意使用了。
然后我们在login方法中编写登陆事件 另外:context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context 使用 ES6 解构
setup (props, { attrs, slots, emit }) { // ... const login = () => { emit('login', { username: state.username, password: state.password }) }
// ...
}
3、 setup()内使用响应式数据时,需要通过.value获取
import { ref } from 'vue'
const count = ref(0) console.log(count.value) // 0
4、从 setup() 中返回的对象上的 property 返回并可以在模板中被访问时,它将自动展开为内部值。不需要在模板中追加 .value
5、setup函数只能是同步的不能是异步的
[3.1 执行时机]
setup 函数会在 beforeCreate 之后、created 之前执行
[3.2 接收 props 数据]
-
在
props中定义当前组件允许外界传递过来的参数名称:props: { p1: String } -
通过
setup函数的第一个形参,接收props数据:setup(props) { console.log(props.p1) }
import { reactive } from "@vue/composition-api";
export default {
setup(props, ctx) {
// console.log("setup");
// console.log(props);
// console.log(ctx);
const state = reactive({ count: 0 });
// console.log(state);
// state.count += 1;
// console.log(state)
return state;
},
beforeCreate() {
// console.log("beforeCreate");
},
created() {
// console.log("created");
},
props: {
p1: String
}
};
[3.3 context]
setup 函数的第二个形参是一个上下文对象,这个上下文对象中包含了一些有用的属性,这些属性在 vue 2.x 中需要通过 this 才能访问到,在 vue 3.x 中,它们的访问方式如下:
const MyComponent = {
setup(props, context) {
context.attrs
context.slots
context.parent
context.root
context.emit
context.refs
}
}
注意:在
setup()函数中无法访问到this
context :上下文,包括 attrs 、 emit 、slots。 ① attrs :在此部分,接收在父组件传递过来的,并且没有在props中声明的参数参数。
② emit:子组件对父组件发送事件 在vue2中,子对父发送事件采用this.$emit对父组件发送事件,在vue3中子组件对父组件发送事件采用context.emit发送事件
// 在子组件中
<button @click="postMsg">
我是子组件,我要向父组件发送事件了
</button>
// js 部分
setup(props,context){
function postMsg(){
console.log('我发送了')
context.emit('pMSg',{msg:'我是子组件发送的信息'})
}
return {postMsg}
}
// 父组件中进行接收
<test @pMSg="func" />
// js 部分
setup(props,context){
const func = (e)=>{
console.log('子组件发送过来的信息',e)
}
return { func }
}
点击按键


[4. reactive]
reactive() 函数接收一个普通对象,返回一个响应式的数据对象。
[4.1 基本语法]
等价于 vue 2.x 中的 Vue.observable() 函数,vue 3.x 中提供了 reactive() 函数,用来创建响应式的数据对象,基本代码示例如下:
import { reactive } from '@vue/composition-api'
// 创建响应式数据对象,得到的 state 类似于 vue 2.x 中 data() 返回的响应式对象
const state = reactive({ count: 0 })
[4.2 定义响应式数据供 template 使用]
-
按需导入
reactive函数:import { reactive } from '@vue/composition-api' -
在
setup()函数中调用reactive()函数,创建响应式数据对象:setup() { // 创建响应式数据对象 const state = reactive({count: 0}) // setup 函数中将响应式数据对象 return 出去,供 template 使用 return state } -
在
template中访问响应式数据:<p>当前的 count 值为:{{count}}</p>
[5. ref]
5.1 基本语法
ref() 函数用来根据给定的值创建一个响应式的数据对象,ref() 函数调用的返回值是一个对象,这个对象上只包含一个 .value 属性:
import { ref } from '@vue/composition-api'
// 创建响应式数据对象 count,初始值为 0
const count = ref(0)
// 如果要访问 ref() 创建出来的响应式数据对象的值,必须通过 .value 属性才可以
console.log(count.value) // 输出 0
// 让 count 的值 +1
count.value++
// 再次打印 count 的值
console.log(count.value) // 输出 1
[5.2 在 template 中访问 ref 创建的响应式数据]
-
在
setup()中创建响应式数据:import { ref } from '@vue/composition-api' setup() { const count = ref(0) return { count, name: ref('zs') } } -
在
template中访问响应式数据:<template> <p>{{count}} --- {{name}}</p> </template>
[5.3 在 reactive 对象中访问 ref 创建的响应式数据]
当把 ref() 创建出来的响应式数据对象,挂载到 reactive() 上时,会自动把响应式数据对象展开为原始的值,不需通过 .value 就可以直接被访问,例如:
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 输出 0
state.count++ // 此处不需要通过 .value 就能直接访问原始值
console.log(count) // 输出 1
注意:新的 ref 会覆盖旧的 ref,示例代码如下:
// 创建 ref 并挂载到 reactive 中
const c1 = ref(0)
const state = reactive({
c1
})
// 再次创建 ref,命名为 c2
const c2 = ref(9)
// 将 旧 ref c1 替换为 新 ref c2
state.c1 = c2
state.c1++
console.log(state.c1) // 输出 10
console.log(c2.value) // 输出 10
console.log(c1.value) // 输出 0
5.4ref获取节点
一;获取单个绑定了ref属性的标签结点
1.给想要获取DOM元素的标签加上 ref 属性,并自定义一个名称
2.在 setup 中使用 ref 声明一个变量名与标签中 ref 属性名称相同的变量
3.之后这个通过 ref 声明的变量中存储的就是 ref 属性标记了的元素DOM结点
<template>
<div ref="getDom"> Ref属性获取dom </div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'Ref',
setup () {
// 先定义一个空的响应式数据ref定义的
// setup中返回该数据,你想获取那个dom元素,在该元素上使用ref属性绑定该数据即可
let getDom= ref(null)
return {
getDom
}
}
}
</script>
二;获取 v-for 循环的多个 ref 结点
1.获取 v-for 循环的结点,需要动态绑定 ref 属性,并定义一个获取对应DOM元素的回调函数。
<template>
<h3 :ref="getlist" v-for="index in 3">我是一组元素{{index}}</h3>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
name: 'Ref',
setup () {
// 获取v-for遍历的元素
// 定义一个空数组,准备接收循环的DOM元素
// 定义动态 ref 所对应的回调函数,往空数组push Dom
let listDom = []
const getlist = (el) => {
listDom.push(el)
}
return {
getlist
}
}
}
</script>
[6. isRef]
isRef() 用来判断某个值是否为 ref() 创建出来的对象;应用场景:当需要展开某个可能为 ref() 创建出来的值的时候,例如:
import { isRef } from 'vue'
const unwrapped = isRef(foo) ? foo.value : foo
[7. toRefs]
toRefs() 函数可以将 reactive() 创建出来的响应式对象,转换为普通的对象,只不过,这个对象上的每个属性节点,都是 ref() 类型的响应式数据,最常见的应用场景如下:
import { toRefs } from '@vue/composition-api'
setup() {
// 定义响应式数据对象
const state = reactive({
count: 0
})
// 定义页面上可用的事件处理函数
const increment = () => {
state.count++
}
// 在 setup 中返回一个对象供页面使用
// 这个对象中可以包含响应式的数据,也可以包含事件处理函数
return {
// 将 state 上的每个属性,都转化为 ref 形式的响应式数据
...toRefs(state),
// 自增的事件处理函数
increment
}
}
页面上可以直接访问 setup() 中 return 出来的响应式数据:
<template>
<div>
<p>当前的count值为:{{count}}</p>
<button @click="increment">+1</button>
</div>
</template>
[8. computed]
computed() 用来创建计算属性,computed() 函数的返回值是一个 ref 的实例。使用 computed 之前需要按需导入:
import { computed } from '@vue/composition-api'
[8.1 创建只读的计算属性]
在调用 computed() 函数期间,传入一个 function 函数,可以得到一个只读的计算属性,示例代码如下:
// 创建一个 ref 响应式数据 const count = ref(1) // 根据 count 的值,创建一个响应式的计算属性 plusOne // 它会根据依赖的 ref 自动计算并返回一个新的 ref const plusOne = computed(() => count.value + 1) console.log(plusOne.value) // 输出 2 plusOne.value++ // error
[8.2 创建可读可写的计算属性]
在调用 computed() 函数期间,传入一个包含 get 和 set 函数的对象,可以得到一个可读可写的计算属性,示例代码如下:
// 创建一个 ref 响应式数据
const count = ref(1)
// 创建一个 computed 计算属性
const plusOne = computed({
// 取值函数
get: () => count.value + 1,
// 赋值函数
set: val => {
count.value = val - 1
}
})
// 为计算属性赋值的操作,会触发 set 函数
plusOne.value = 9
// 触发 set 函数后,count 的值会被更新
console.log(count.value) // 输出 8
[9. watch]
watch() 函数用来监视某些数据项的变化,从而触发某些特定的操作,使用之前需要按需导入:
import { watch } from '@vue/composition-api'
[9.1 基本用法]
const count = ref(0)
// 定义 watch,只要 count 值变化,就会触发 watch 回调
// watch 会在创建时会自动调用一次
watch(() => console.log(count.value))
// 输出 0
setTimeout(() => {
count.value++
// 输出 1
}, 1000)
// Vue2使用watch
<template>
<div>总合:{{ sum }}<button @click="sum++">点击累加</button></div>
</template>
<script>
import { ref } from "vue";
export default {
// vue2中使用watch
watch: {
sum: {
deep: true,
handler(newValue, oldValue) {
console.log("总合 sum 变化:", newValue, oldValue);
},
},
},
setup() {
let sum = ref(0);
return {
sum,
};
},
};
</script>
<style>
</style>
// Vue3使用watch
// watch有三个参数:
// 参数1:监听的参数
// 参数2:监听的回调函数
// 参数3:监听的配置(immediate)
// =============================情况1
// 监视ref所定义的一个响应式数据
<template>
<div>总合:{{ sum }}<button @click="sum++">点击累加</button></div>
</template>
// 监视ref所定义的一个响应式数据
<script>
import { ref, watch } from "vue";
export default {
setup() {
let sum = ref(0);
// 监视ref所定义的一个响应式数据
watch(sum, (newValue, oldValue) => {
console.log("sum ==> ", newValue, oldValue);
});
return {
sum,
};
},
};
</script>
// result
// sum ==> 1 0
// sum ==> 2 1
// sum ==> 3 2
// sum ==> 4 3
// sum ==> 5 4
// sum ==> 6 5
// ==========================情况2
// 监视ref所定义的多个响应式数据
<template>
<div>总合:{{ sum }}<button @click="sum++">点击累加</button></div>
<hr />
<div>
msg:{{ msg }}
<button @click="msg += '~'">改变msg</button>
</div>
</template>
<script>
import { ref, watch } from "vue";
export default {
setup() {
let sum = ref(0);
let msg = ref("watch使用"):
// 情况2:监视ref所定义的多个响应式数据
watch([sum, msg], (newValue, oldValue) => {
console.log("sum/msg ==> ", newValue, oldValue);
},{immediate:true});
return {
sum,
msg,
};
},
};
</script>
// ===========================情况3
// 监视reactive所定义的一个响应式数据
// 注意:
// 这里无法正确获取oldValue
// 强制开启了深度监听(deep配置不生效)
<template>
<div>
<h3>情况3::监视reactive所定义的一个响应式数据</h3>
<div>姓名:{{person.name}}</div>
<div>年龄:{{person.age}}</div>
<button @click="person.name += '~'">修改姓名</button>
<button @click="person.age ++">修改年龄</button>
</div>
</template>
<script>
import { ref, watch,reactive } from "vue";
export default {
setup() {
let person = reactive({
name: "lisa",
age: 18,
job: {
joblist: {
money: 10,
},
},
});
// 情况3、监视reactive所定义的一个响应式数据
/*
若watch监视的是reactive定义的响应式数据,则无法正确获得oldvalue!
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
*/
watch(person,(newValue, oldValue) => {
console.log("person ==> ", newValue, oldValue);
},{immediate:true,deep:false}//这里的deep配置不再奏效
);
return {
person,
};
},
};
</script>
// ======================情况4
// 监视reactive所定义的一个响应式数据中的某个属性
<template>
<div>
<h3>情况4::监视reactive所定义的一个响应式数据中的某个属性</h3>
<div>姓名:{{person.name}}</div>
<div>年龄:{{person.age}}</div>
<button @click="person.name += '~'">修改姓名</button>
<button @click="person.age ++">修改年龄</button>
</div>
</template>
<script>
import { ref, watch,reactive } from "vue";
export default {
setup() {
let person = reactive({
name: "lisa",
age: 18,
job: {
joblist: {
money: 10,
},
},
});
// 情况4、监视reactive所定义的一个响应式数据中的某个属性
watch(()=>person.name,(newValue, oldValue) => {
console.log("person.name ==> ", newValue, oldValue);
});
return {
person,
};
},
};
</script>
// ================情况5
// 监视reactive所定义的一个响应式数据中的某些属性
<template>
<div>
<h3>情况4::监视reactive所定义的一个响应式数据中的某个属性</h3>
<div>姓名:{{person.name}}</div>
<div>年龄:{{person.age}}</div>
<button @click="person.name += '~'">修改姓名</button>
<button @click="person.age ++">修改年龄</button>
</div>
</template>
<script>
import { ref, watch,reactive } from "vue";
export default {
setup() {
let person = reactive({
name: "lisa",
age: 18,
job: {
joblist: {
money: 10,
},
},
});
// 情况5、监视reactive所定义的一个响应式数据中的某些属性
watch([()=>person.name,()=>person.age],(newValue, oldValue) => {
console.log("person.name/person.age ==> ", newValue, oldValue);
});
return {
person,
};
},
};
</script>
// =================特殊情况
// watch监听reactive中对象的嵌套对象
<template>
<div>
<div>姓名:{{person.name}}</div>
<div>年龄:{{person.age}}</div>
<div>薪资:{{person.job.joblist.money}} K</div>
<button @click="person.name += '~'">修改姓名</button>
<button @click="person.age ++">修改年龄</button>
<button @click="person.job.joblist.money ++">提薪</button>
</div>
</template>
<script>
import { ref, watch,reactive } from "vue";
export default {
setup() {
let person = reactive({
name: "lisa",
age: 18,
job: {
joblist: {
money: 10,
},
},
});
// 特殊情况、监视reactive所定义嵌套对象
watch(()=>person.job,(newValue, oldValue) => {
console.log("person.job对象发生变化 ==> ", newValue, oldValue);
},{deep:true});//此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
return {
person,
};
},
};
</script>
[9.2 清除监视]
在 setup() 函数内创建的 watch 监视,会在当前组件被销毁的时候自动停止。如果想要明确地停止某个监视,可以调用 watch() 函数的返回值即可,语法如下:
// 创建监视,并得到 停止函数
const stop = watch(() => {
/* ... */
})
// 调用停止函数,清除对应的监视
stop()
[9.3 在 watch 中清除无效的异步任务]
有时候,当被 watch 监视的值发生变化时,或 watch 本身被 stop 之后,我们期望能够清除那些无效的异步任务,此时,watch 回调函数中提供了一个 cleanup registrator function 来执行清除的工作。这个清除函数会在如下情况下被调用:
-
watch 被重复执行了
-
watch 被强制
stop了
Template 中的代码示例如下:
/* template 中的代码 */ <input type="text" v-model="keywords" />
Script 中的代码示例如下:
// 定义响应式数据 keywords
const keywords = ref('')
// 异步任务:打印用户输入的关键词
const asyncPrint = val => {
// 延时 1 秒后打印
return setTimeout(() => {
console.log(val)
}, 1000)
}
// 定义 watch 监听
watch(
keywords,
(keywords, prevKeywords, onCleanup) => {
// 执行异步任务,并得到关闭异步任务的 timerId
const timerId = asyncPrint(keywords)
// 如果 watch 监听被重复执行了,则会先清除上次未完成的异步任务
onCleanup(() => clearTimeout(timerId))
},
// watch 刚被创建的时候不执行
{ lazy: true }
)
// 把 template 中需要的数据 return 出去
return {
keywords
}
6122

被折叠的 条评论
为什么被折叠?



