render 中的 h 函数

h 函数可以创建虚拟 dom,通过创建的虚拟 dom 再转化为真的的 DOM,从而渲染到页面中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script>
// render function
// template -> render -> h -> 虚拟DOM(JS对象)-> 真实 DOM -> 展示到页面上
const app = Vue.createApp({
template: `
<my-title :level="2">
hello xiaokang
</my-title>
`
})

app.component('my-title', {
props: ['level'],
render() {
const { h } = Vue
return h('h' + this.level, {}, [
this.$slots.default(),
h('h4', {}, 'xiaokang')
])
}
})

const vm = app.mount('#root')
</script>

h 函数第一个参数为tag name,也就是你需要创建的标签名,第二参数为此标签上的属性,第三个参数为其内容数组。

image-20210319172822390

参考链接:https://vue3js.cn/docs/zh/guide/render-function.html

插件

vue 中插件主要用于把一些通用性的功能封装起来

插件定义的基本语法

1
2
3
const myPlugin = {
install(app, options) {}
}

当使用插件时,会调用插件的 install 方法。第一个参数 app 为生成的 app 对象,第二个参数为传递的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<script>
// plugin 插件, 也是把通用性的功能封装起来
const myPlugin = {
install(app, options) {
// 提供和注入
app.provide('name', 'xiaokang')
// 自定义指令
app.directive('focus', {
mounted(el) {
el.focus()
}
})
// 混入
app.mixin({
mounted() {
console.log('mixin')
}
})
// 全局属性
app.config.globalProperties.$sayHello = 'hello world'
}
}

const app = Vue.createApp({
template: `
<my-title />
`
})

app.component('my-title', {
inject: ['name'],
mounted() {
console.log(this.$sayHello)
},
template: `<div>{{name}}<input v-focus /></div>`
})

app.use(myPlugin, { name: 'xiaokang' })

const vm = app.mount('#root')
</script>

数据校验插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const app = Vue.createApp({
data() {
return { name: 'dell', age: 23 }
},
rules: {
age: {
validate: (age) => age > 25,
message: 'too young, to simple'
},
name: {
validate: (name) => name.length >= 4,
message: 'name too short'
}
},
template: `
<div>name:{{name}}, age:{{age}}</div>
`
})

// 对数据做校验的插件
const validatorPlugin = (app, options) => {
app.mixin({
created() {
for (let key in this.$options.rules) {
const item = this.$options.rules[key]
this.$watch(key, (value) => {
const result = item.validate(value)
if (!result) console.log(item.message)
})
}
}
})
}

app.use(validatorPlugin)
const vm = app.mount('#root')

参考:https://vue3js.cn/docs/zh/guide/plugins.html

Teleport 传送门-vue3

主要用于将 dom 元素挂载到其他位置,例如挂载到head里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<body>
<div id="root"></div>
</body>
<script>
// teleport 传送门
const app = Vue.createApp({
data() {
return {
show: false,
message: 'hello'
}
},
methods: {
handleBtnClick() {
this.show = !this.show
}
},
template: `
<div class="area">
<button @click="handleBtnClick">按钮</button>
<teleport to="body">
<div class="mask" v-show="show">{{message}}</div>
</teleport>
</div>
`
})

const vm = app.mount('#root')
</script>

image-20210319171148266

components API-vue3

setup 函数

该函数会在创建组件之前执行,由于在执行 setup 时尚未创建组件实例,因此在 setup 选项中没有 this。这意味着,除了 props 之外,你将无法访问组件中声明的任何属性——本地状态计算属性方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 对数据做校验的插件
const app = Vue.createApp({
template: `
<div @click="handleClick">{{name}}</div>
`,
methods: {
test() {
console.log(this.$options.setup())
}
},
// 实例可以调用setup方法
mounted() {
this.test()
},
// 这里不能调用this
setup(props, context) {
return {
name: 'xiaokang',
handleClick: () => {
alert(123)
}
}
}
})
const vm = app.mount('#root')

setup 里返回的内容可以在实例中调用。

响应式引用与只读

主要用于将setup中的变量转换为响应式的变量。默认变量并不是响应式的。

其中ref处理基础类型的数据,reactive用于处理非基础类型的数据(对象和数组)。

方法名作用
ref将基础类型数据转化为响应式数据
reactive将非基础类型的数据(对象和数组)。
readonly将数据设为只读
toRefs将非基础类型的子元素设置为响应式数据
  1. 基础类型引用

    1
    2
    3
    4
    5
    6
    7
    8
    setup(props, context) {
    const { ref } = Vue
    let name = ref('1')
    setTimeout(() => {
    name.value = '2'
    }, 2000)
    return { name }
    }

    通过 ref 包装后,name 实则变成了proxy({value: '2'})这样的引用,当修改值时需要修改 name 的 value 属性。但是调用时不需要使用value,vue 会识别并自动调用。

  2. 非基础类型引用

    1
    2
    3
    4
    5
    6
    7
    8
    setup(props,context){
    const { reactive, readonly, toRefs } = Vue
    const nameObj = reactive({ name: 'xiaokang', age: 21 })
    setTimeout(() => {
    nameObj.age = '22'
    }, 2000)
    return { nameObj }
    }

    通过reactive包装后,nameObj 就是响应式的了。

  3. 组合使用

    在非响应式引用里,只有整个对象是响应式的,而对象里的某个属性并不是响应式的,因此,需要将这个对象再次进行包装才可以使其属性变成响应式的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    setup(props,context){
    const { reactive, readonly, toRefs } = Vue
    const nameObj = reactive({ name: 'xiaokang', age: 21 })
    setTimeout(() => {
    nameObj.age = '22'
    }, 2000)
    const { name, age } = toRefs(nameObj)
    return { age }
    }
  4. 只读

    1
    2
    3
    4
    5
    6
    const nameObj = reactive({ name: 'xiaokang', age: 22 })
    const nameObjCopy = readonly(nameObj)
    setTimeout(() => {
    nameObj.age = 21
    nameObjCopy.age = 23
    }, 2000)

参考:https://vue3js.cn/docs/zh/guide/composition-api-introduction.html#带-ref-的响应式变量

toRef

可以用来为源响应式对象上的 property 性创建一个 ref。然后可以将 ref 传递出去,从而保持对其源 property 的响应式连接。

1
2
3
4
5
6
7
8
9
10
setup(props, context) {
const { reactive, toRef } = Vue
const nameObj = reactive({ name: 'xiaokang' })
const age = toRef(data, 'age')
setTimeout(() => {
age.value = 21
}, 2000)

return { age }
}

setup 中 context 参数

context 参数一共可以结构出三个参数:

  • attrs

    None-Props 属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const app = Vue.createApp({
    template: `
    <child app='app' style='color:red'></child>
    `
    })
    app.component('child', {
    template: '<div>child</div>',
    setup(props, context) {
    const { attrs, slots, emit } = context
    console.log(attrs) // 接收没有被props接收的属性
    }
    })
    const vm = app.mount('#root')

    image-20210319190338185

  • slots

    插槽

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const app = Vue.createApp({
    template: `
    <child app='app' style='color:red'>
    插槽内容
    </child>
    `
    })
    app.component('child', {
    setup(props, context) {
    const { h } = Vue
    const { attrs, slots, emit } = context
    return () => h('div', {}, slots.default())
    }
    })
    const vm = app.mount('#root')

    可以使用jsx进行模板渲染:https://github.com/vuejs/jsx-next#installation

  • emit

    触发自定义事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    const app = Vue.createApp({
    template: `
    <child @change='handleChange' app='app' style='color:red'>
    插槽内容
    </child>
    `,
    methods: {
    handleChange() {
    console.log(123)
    }
    }
    })
    app.component('child', {
    template: "<div @click='handleClick'>123123</div>",
    setup(props, context) {
    const { h } = Vue
    const { attrs, slots, emit } = context
    function handleClick() {
    emit('change')
    }
    return {
    handleClick
    }
    }
    })
    const vm = app.mount('#root')

计算属性

计算属性同样使用Vue对象提供的computed方法。computed 方法接收参数有两种类型,一种是函数,另一种是对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
setup(props, context) {
const { ref, computed } = Vue
let number = ref(1)
let number2 = ref(0)
const handle = () => {
number.value += 1
number2.value += 1
}
// 传入函数
let cNumber = computed(() => {
return number.value + 5
})
// 传入对象
let cNumberObj = computed({
get: () => {
return number2.value + 5
},
set: (val) => {
number2.value = val - 1
}
})
return { number, cNumber, handle, cNumberObj }
}

watch 与 watchEffect

就像我们如何使用 watch 选项在组件内的 user property 上设置侦听器一样,我们也可以使用从 Vue 导入的 watch 函数执行相同的操作。它接受 3 个参数:

  • 一个响应式引用或我们想要侦听的 getter 函数
  • 一个回调函数
  • 可选的配置选项
  1. 侦听单个源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 侦听一个 getter
    const state = reactive({ count: 0 })
    watch(
    () => state.count,
    (count, prevCount) => {
    /* ... */
    }
    )

    // 直接侦听ref
    const count = ref(0)
    watch(count, (count, prevCount) => {
    /* ... */
    })
  2. 侦听多个源

    1
    2
    3
    watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
    /* ... */
    })
不同点watchwatchEffect
惰性默认情况下是惰性的,但第三个参数传入immediate为 true 可以立即执行非惰性
能拿到原始值和当前值只能拿到当前值
只可以侦听多个数据可以可以
1
2
3
4
5
const stop = watchEffect(() => {
// 会自动判断依赖并更新
console.log(nameObj.name)
})
stop() // 调用后会停止监听

生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
setup() {
const {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onRenderTracked,
onRenderTriggered
} = Vue
const name = ref('dell')
onBeforeMount(() => {
console.log('onBeforeMount')
})
onMounted(() => {
console.log('onMounted')
})
onBeforeUpdate(() => {
console.log('onBeforeUpdate')
})
onUpdated(() => {
console.log('onUpdated')
})
// 每次渲染后重新收集响应式依赖
onRenderTracked(() => {
console.log('onRenderTracked')
})
// 每次触发页面重新渲染时自动执行
onRenderTriggered(() => {
console.log('onRenderTriggered')
})
const handleClick = () => {
name.value = 'lee'
}
return { name, handleClick }
},

Provide、Inject 和 ref

提供与注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const app = Vue.createApp({
setup() {
const { provide, ref, readonly } = Vue
const name = ref('name1')
provide('name', readonly(name))
provide('changeName', (value) => {
name.value = value
})
return {}
},
template: `
<div>
<child />
</div>
`
})

app.component('child', {
setup() {
const { inject } = Vue
const name = inject('name')
const changeName = inject('changeName')
const handleClick = () => {
changeName('name2')
}
return { name, handleClick }
},
template: '<div @click="handleClick">{{name}}</div>'
})

通过readonly对提供的变量进行包装,实现数据单向流(子组件不能修改父组件的值)。

ref

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const app = Vue.createApp({
setup() {
const { ref, onMounted } = Vue
const hello = ref(null)
onMounted(() => {
console.log(hello.value) // dom节点
})
return { hello }
},
template: `
<div>
<div ref="hello">hello world</div>
</div>
`
})