setup函数作用
组件的render函数每次更新时都会重新执行,但是setup函数只会在组件挂载时执行一次。
- setup函数是compositionAPI的入口
- 可以在函数内部编写逻辑,解决vue2中反复横跳问题
- setup返回函数时为组件的render函数,返回对象时对象中的数据将暴露给模板使用
- setup中函数的参数为props、context({slots,emit,attrs,expose})
const mountComponent = (n2,container,anchor)=>{
let {render,data=()=>({}),props:propsOptions={},setup} = n2.type;
const state = reactive(data())
const instance = {
state, // 组件的状态
isMounted:false, // 组件是否挂载
subTree:null, // 子树
update:null,
attrs:{},
props:{},
next:null,
setupState:null,
vnode:n2
}
n2.component = instance;
// 用户写的props 及 传入的props
initProps(instance,propsOptions,n2.props); // 初始化属性
if(setup){ // 对setup做相应处理
const setupContext = {};
const setupResult = setup(instance.props,setupContext);
if(isFunction(setupResult)){
render = setupResult;
}else if(isObject(setupResult)){
instance.setupState = proxyRefs(setupResult)
}
}
const renderContext = new Proxy(instance,{
get(target,key){
const {state,props,setupState} = target;
if(state && hasOwn(state,key)){
return state[key];
}else if(hasOwn(props,key)){
return props[key];
}else if(setupState && hasOwn(setupState,key)){ // setup返回值做代理
return setupState[key];
}
const publicGetter = publicPropertiesMap[key];
if(publicGetter){
return publicGetter(instance)
}
},
set(target,key,value){
const {state,props} = target;
if(state && hasOwn(state,key)){
state[key] = value;
return true;
}else if(hasOwn(props,key)){
console.warn(`Attempting to mutate prop "${key}". Props are readonly.`)
return false;
}
return true;
}
});
}
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
实现emit方法
const VueComponent = {
setup(props,ctx){
const handleClick = ()=>{
ctx.emit('myEvent');
}
return ()=>h('button',{onClick:handleClick},'点我啊')
}
}
const app = createApp(h(VueComponent,{onMyEvent:()=>{alert(1000)}}))
app.mount(document.getElementById('app'))
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
const setupContext = {
attrs:instance.attrs,
emit:(event,...args)=>{
const eventName = `on${event[0].toUpperCase() + event.slice(1)}`;
const handler = instance.vnode.props[eventName]; // 找到绑定的方法
// 触发方法执行
handler && handler(...args);
}
};
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
slot实现
const MyComponent = {
render(){
return h(Fragment,[
h('div',[this.$slots.header()]), // 获取插槽渲染
h('div',[this.$slots.body()]),
h('div',[this.$slots.footer()]),
])
}
}
const VueComponent = {
setup(){
return ()=>h(MyComponent,{ // 渲染组件时传递对应的插槽属性
header:() => h('p','头'),
body:() => h('p','体'),
footer:() => h('p','尾')
})
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export const createVNode = (type,props,children = null)=>{
// ....
if(children){
let type = 0;
if(Array.isArray(children)){
type = ShapeFlags.ARRAY_CHILDREN;
}else if(isObject(children)){ // 类型是插槽
type = ShapeFlags.SLOTS_CHILDREN
}else{
children = String(children);
type = ShapeFlags.TEXT_CHILDREN
}
vnode.shapeFlag |= type
}
return vnode;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const publicPropertiesMap = {
$attrs:i=> i.attrs,
$slots:i=>i.slots
}
function initSlots(instance,children){
if(instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN){
instance.slots = children;
}else{
instance.slots = {};
}
}
const mountComponent = (n2,container,anchor)=>{
const instance = {
// ...
slots:null
}
// ...
initProps(instance,propsOptions,n2.props);
initSlots(instance,n2.children) // 初始化插槽
if(setup){ // 对setup做相应处理
const setupContext = {
// ...
slots: instance.slots // 挂载插槽
};
}
}
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
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
生命周期实现原理
export let currentInstance = null
export function setCurrentInstance(instance){
currentInstance = instance // 用于记住当前实例
}
1
2
3
4
2
3
4
setCurrentInstance(instance); // 在调用setup的时候保存当前实例
const setupResult = setup(instance.props,setupContext);
setCurrentInstance(null);
1
2
3
2
3
创建生命周期钩子
export const enum LifecycleHooks {
BEFORE_MOUNT = 'bm',
MOUNTED = 'm',
BEFORE_UPDATE = 'bu',
UPDATED = 'u'
}
function createHook(type){
return (hook,target = currentInstance) =>{ // 调用的时候保存当前实例
if(target){
const hooks = target[type] || (target[type] = []);
const wrappedHook = () =>{
setCurrentInstance(target); // 当生命周期调用时 保证currentInstance是正确的
hook.call(target);
setCurrentInstance(null);
}
hooks.push(wrappedHook);
}
}
}
export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);
export const onMounted = createHook(LifecycleHooks.MOUNTED);
export const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);
export const onUpdated = createHook(LifecycleHooks.UPDATED);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
钩子调用
const componentUpdateFn = ()=>{
if(!instance.isMounted){
const {bm,m} = instance
if(bm){ // beforeMount
invokeArrayFns(bm)
}
const subTree = render.call(renderContext,renderContext);
patch(null,subTree,container,anchor);
if(m){ // mounted
invokeArrayFns(m)
}
instance.subTree = subTree
instance.isMounted = true;
}else{
let {next,bu,u} = instance;
if(next){
updateComponentPreRender(instance,next)
}
if(bu){ // beforeUpdate
invokeArrayFns(bu)
}
const subTree = render.call(renderContext,renderContext);
patch(instance.subTree,subTree,container,anchor)
if(u){ // updated
invokeArrayFns(u)
}
instance.subTree = subTree
}
}
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
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
export const invokeArrayFns = (fns) => {
for (let i = 0; i < fns.length; i++) {
fns[i](); // 调用钩子方法
}
}
1
2
3
4
5
2
3
4
5