一、渲染组件
组件的vnode如下
const MyComponent = {
name: "MyComponent",
data() {
return {foo: 1}
},
render() {
return {
type: "div",
children: this.foo,
}
},
created() {},
beforeCreate() {},
mounted() {},
beforeMount() {},
updated() {},
beforeUpdate() {}
}
const vnode = {
type: MyComponent
}
renderer.render(vnode, document.getElementById("app"))
在patch添加组件类型的判断
function patch(n1, n2, container, anchor) {
if(n1 && n1.type !== n2.type) {
unmount(n1);
n1 = null;
}
let { type } = n2;
if(typeof type === "string") {
// 普通标签元素
} else if(typeof type === "object"){
if(!n1) {
mountComponent()
} else {
patchComponent()
}
} else if(type === Text){
// 文本节点
} else if(type === Fragment) {
// 片段节点
} else {
// 省略了其他类型的vnode
}
}
mountComponent的实现
function mountComponent(vnode, container, anchor) {
const componentOptions = vnode.type;
const { render } = componentOptions;
let subTree = render();
patch(null, subTree, container, anchor);
}
二、组件状态和自更新
1、组件自身状态的初始化
function mountComponent(vnode, container, anchor) {
const componentOptions = vnode.type;
const { render, data } = componentOptions;
let state = reactive(data())
let subTree = render.call(state, state); // render函数内部可以通过this访问组件自身状态数据
patch(null, subTree, container, anchor);
}
2、自更新
当组件自身状态发生变化时, 需要有能力触发组件更新
function mountComponent(vnode, container, anchor) {
const componentOptions = vnode.type;
const { render, data } = componentOptions;
let state = reactive(data())
effect(() => {
let subTree = render.call(state, state); // render函数内部可以通过this访问组件自身状态数据
patch(null, subTree, container, anchor);
})
}
优化: 无论对响应式数据进行多少次修改, 副作用函数只会重新执行一次, 需要对effec函数添加调度器
let isFlushing = false;
let queue = new Set();
let p = Promise.resolve();
function queueJob(job) {
queue.add(job)
if(!isFlushing) {
isFlushing = true;
p.then(() => {
try {
queue.forEach(job => job())
} finally {
isFlushing = false;
queue.clear() // 任务执行完成后需要清除
}
})
}
}
function mountComponent(vnode, container, anchor) {
const componentOptions = vnode.type;
const { render, data } = componentOptions;
let state = reactive(data())
effect(() => {
let subTree = render.call(state, state); // render函数内部可以通过this访问组件自身状态数据
patch(null, subTree, container, anchor);
}, {
scheduler: queueJob,
})
}
三、组件实例与组件的生命周期
3.1组件示例instance
数据更新的时, 需要新的subTree和上一次组件所渲染的subTree进打补丁
function mountComponent(vnode, container, anchor) {
const componentOptions = vnode.type;
const { render, data } = componentOptions
let state = reactive(data())
const instance = {
state,
isMounted: false,
subTree: null,
}
vnode.component = instance;
effect(() => {
let subTree = render.call(state, state); // render函数内部可以通过this访问组件自身状态数据
if(!instance.isMounted) {
patch(null, subTree, container, anchor);
instance.isMounted = true;
} else {
patch(instance.subTree, subTree, container, anchor)
}
instance.subTree = subTree
}, {
scheduler: queueJob,
})
}
- state: 组件自身的状态数据, 即data
- isMounted: 组件是否被挂载
- subTree: 组件的渲染函数返回的虚拟DOM, 即组件的subTree
3.2、组件生命周期
function mountComponent(vnode, container, anchor) {
const componentOptions = vnode.type;
const { render, data, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated } = componentOptions
beforeCreate && beforeCreate();
let state = reactive(data())
const instance = {
state,
isMounted: false,
subTree: null,
}
vnode.instance = instance;
created && created();
effect(() => {
let subTree = render.call(state, state); // render函数内部可以通过this访问组件自身状态数据
if(!instance.isMounted) {
beforeMount && beforeMount()
patch(null, subTree, container, anchor);
instance.isMounted = true;
mounted && mounted();
} else {
beforeUpdate && beforeUpdate()
patch(instance.subTree, subTree, container, anchor);
updated && updated();
}
instance.subTree = subTree
}, {
scheduler: queueJob,
})
}
实际上,由于可能存在多个同样的组件生命周期钩子, 比如mixins的钩子, 通常需要将组件生命周期的钩子序列化为一个数组
四、props与组件的被动更新
4.1、props
<my-component title="A big title" :other="val" />
// 渲染到页面上
<div>`count is A big title</div>
对应的vnode
const MyComponent = {
render() {
return {
type: "div",
children: `count is${this.title}`
}
},
props: {
title: "String"
}
}
const vnode = {
type: MyComponent,
props: {
title: "A big Title",
other: this.value
}
}
props有两部分构成
- 为组件传递的props数据, 即vnode.props
- 组件对象中定义的props选项, 即MyComponent.props
重点: 将vnode.props中, 属MyComponent.props和不属于MyComponent.props区分出来
function mountComponent(vnode, container, anchor) {
const componentOptions = vnode.type;
const { render, data, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, props: propsOption} = componentOptions
beforeCreate && beforeCreate();
let state = reactive(data())
let [ props, attrs ] = resolveProps(propsOption, vnode.props);
const instance = {
state,
props: shallowReactive(props),
isMounted: false,
subTree: null,
}
vnode.component = instance;
created && created();
effect(() => {
let subTree = render.call(state, state); // render函数内部可以通过this访问组件自身状态数据
if(!instance.isMounted) {
beforeMount && beforeMount()
patch(null, subTree, container, anchor);
instance.isMounted = true;
mounted && mounted();
} else {
beforeUpdate && beforeUpdate()
patch(instance.subTree, subTree, container, anchor);
updated && updated();
}
instance.subTree = subTree
}, {
scheduler: queueJob,
})
}
// 用于解析组件props和attrs数据
function resolveProps(option, propsData) {
let props = {};
let attrs = {};
for(let key in option) {
if(key in propsData) {
props[key] = propsData;
} else {
attrs[key] = option[key];
}
}
return [props, attrs]
}
注意:还有默认值、类型校验的处理
4.2、被动更新
参数更新 -> 触发effect函数 -> 已经mounted过 -> 进行patch -> patchComponent
function hasPropsChanged(prevProps, nextProps) {
const prevKey = Object.keys(prevProps).length;
const nextKey = Object.keys(nextProps).length;
if(prevKey !== nextKey) return true;
for(let key in prevKey) {
if(nextKey[key] !== prevKey[key]) return true;
}
return false;
}
function patchComponent(n1, n2, container) {
const instance = n2.component = n1.component; // 获取组件实例, 并且给新的vnode绑定实例
const {props} = instance; // 获取当前的props;
if(hasPropsChanged(n1.props, n2.props)) { // 检测props是否发生变化, 如果没有变化则不需要更新
let [nextProps] = resolveProps(n2.type.props, n2.props);
// 更新props
for(let key in nextProps) {
props[key] = nextProps[key];
}
// 删除不要的props
for(let key in props) {
if(!(key in nextProps)) delete props[key];
}
}
}
其中: props数据与组件自身的状态数据都需要暴露到渲染函数中, 并使得渲染函数能够通过this访问他们
解决方法: 封装一个渲染上下文, 并且进行代理
const renderContext = new Proxy(instance, {
get(target, p, receiver) {
const {state, props} = target;
if(state && p in state) {
return state[p]
} else if(p in props) {
return props[p]
} else {
console.log("不存在")
}
},
set(target, p, value, receiver) {
const {state, props} = target;
if(state && p in state) {
state[p] = value;
} else if(p in props) {
console.log("props is readonly")
} else {
console.log("不存在");
}
}
});
function mountComponent(vnode, container, anchor) {
const componentOptions = vnode.type;
const { render, data, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, props: propsOption} = componentOptions
beforeCreate && beforeCreate();
let state = reactive(data())
let [ props, attrs ] = resolveProps(propsOption, vnode.props);
const instance = {
state,
props: shallowReactive(props),
isMounted: false,
subTree: null,
}
vnode.component = instance;
const renderContext = new Proxy(instance, {
get(target, p, receiver) {
const {state, props} = target;
if(state && p in state) {
return state[p]
} else if(p in props) {
return props[p]
} else {
console.log("不存在")
}
},
set(target, p, value, receiver) {
const {state, props} = target;
if(state && p in state) {
state[p] = value;
} else if(p in props) {
console.log("props is readonly")
} else {
console.log("不存在");
}
}
});
created && created();
effect(() => {
let subTree = render.call(renderContext, state); // render函数内部可以通过this访问组件自身状态数据
if(!instance.isMounted) {
beforeMount && beforeMount()
patch(null, subTree, container, anchor);
instance.isMounted = true;
mounted && mounted();
} else {
beforeUpdate && beforeUpdate()
patch(instance.subTree, subTree, container, anchor);
updated && updated();
}
instance.subTree = subTree
}, {
scheduler: queueJob,
})
}
注意: 除了组件自身数据、props数据外, 完整的组件还包含methods, computed等选项中定义的数据和方法,这些都需要在渲染上下文对象中处理
五、setup函数的作用与实现
function mountComponent(vnode, container, anchor) {
const componentOptions = vnode.type;
const { setup, render, data, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, props: propsOption} = componentOptions
beforeCreate && beforeCreate();
let state = data ? reactive(data()) : null;
let [ props, attrs ] = resolveProps(propsOption, vnode.props);
const instance = {
state,
props: shallowReactive(props),
isMounted: false,
subTree: null,
}
const setupContext = {attrs};
const setupResult = setup(shallowReadonly(vnode.props), setupContext); // 参数1: 只读的props,防止被更改, 参数2: 上下文,包括 slot, emit , attrs, expose
let setupState = null
if(typeof setupResult === "function") {
// 如果setup返回的值是函数,则如果存在render则报告冲突,将该函数作为render函数使用
if(render) console.error("setup 函数返回渲染函数, render函数将被忽略");
} else {
// 如果setup返回的值不是函数,则作为数据状态赋值给setupState
setupState = setupResult;
}
vnode.component = instance;
const renderContext = new Proxy(instance, {
get(target, p, receiver) {
const {state, props} = target;
if(state && p in state) {
return state[p]
} else if(p in props) {
return props[p]
} else if(setupState && p in setup) {
return setupState[p] // 渲染上下文增加对setupState的支持
} else {
console.log("不存在")
}
},
set(target, p, value, receiver) {
const {state, props} = target;
if(state && p in state) {
state[p] = value;
} else if(p in props) {
console.log("props is readonly")
} else if (setupState && p in setupState){
setupState[p] = value; // 渲染上下文增加对setupState的支持
} else {
console.log("不存在");
}
}
});
created && created();
effect(() => {
let subTree = render.call(renderContext, state); // render函数内部可以通过this访问组件自身状态数据
if(!instance.isMounted) {
beforeMount && beforeMount()
patch(null, subTree, container, anchor);
instance.isMounted = true;
mounted && mounted();
} else {
beforeUpdate && beforeUpdate()
patch(instance.subTree, subTree, container, anchor);
updated && updated();
}
instance.subTree = subTree
}, {
scheduler: queueJob,
})
}
setup的返回结果
-
函数,函数将作为组件的render使用
const Comp = { setup() { return () => { return {type: "div", children: "text1"} } }, render() { return {type: "div", children: "text2"} // 这个会被覆盖 } } -
返回一个对象, 该对象中包含的数据将暴露给模板使用
const Comp = { setup() { let count = ref(0) return {count} }, render() { return {type: "div", children: `${this.count}`} } }
setup中接受的参数
const Comp = {
setup(props, setupContext) {
let value = props.data;
let {attrs, slot, emit, expose} = setupContext;
},
}
- props对象,
- setupContext
- slots 组件接收到的插槽
- emit 用于发射自定义事件
- attrs 没有显式声明为props的属性
- expose 用来显式地对外暴露组件数据
六、组件事件与emit的实现
function emit(event, ...payload) {
let eventName = `on${event[0].toUpperCase()}${event.slice(0)}`; // change => onChange
const handler = props[eventName];
if(handler) {
handler(...payload);
} else {
console.log("事件不存在")
}
}
const setupContext = {attrs, emit};
上面有个问题, 拿的是props中值, 但是之前的逻辑, 如果没有type.props中定义, 那么会被划分到attrs, 所以拆解props的代码需要修改
function resolveProps(option, propsData) {
let props = {};
let attrs = {};
for(let key in option) {
if(key in propsData || key.startsWith("on") { // 以字符串on开头的props, 不管是否显式声明, 都将其添加到props数据中, 而非attrs
props[key] = propsData;
} else {
attrs[key] = option[key];
}
}
return [props, attrs]
}
七、插槽的工作原理与实现
// MyComponent.vue
<template>
<header><slot name= "header"/></header>
<div>
<slot name="body"></slot>
</div>
<footer><slot name="footer"></footer>
</template>
// index.vue
<MyComponent>
<template #header>
<h1>我是标题</h1>
</template>
<template #body>
<section>我是内容</section>
</template>
<template #footer>
<p>我是注脚</p>
</template>
</MyComponent>
编译后
// myComponent的渲染函数
function render() {
return [
{
type: 'header',
children: [this.$slots.header()]
},
{
type: 'body',
children: [this.$slots.body()]
},
{
type: 'footer',
children: [this.$slots.footer()]
}
]
}
// index.vue的渲染函数
function render() {
return {
type: MyComponent,
// 组件的 children 会被编译成一个对象
children: {
header() {
return { type: 'h1', children: '我是标题' }
},
body() {
return { type: 'section', children: '我是内容' }
},
footer() {
return { type: 'p', children: '我是注脚' }
}
}
}
}
所以slot的获取可以通过下面方式获取
const slots = vnode.children || []; // 父节点的中关于子节点的children才是子节点的slot,
// vnode.children 节点插槽
// vnode.type.chidlren 节点内部实现, 包括插槽的定义
八、注册生命周期
- 一个组件可以注册多个onMounted
- 每个组件都可以注册属于自己的onMounted;
像前面的activedEffect一样, 组件也可以设置一个currentInstance
let currentInstance = null;
function setCurrentInstance(instance) {
currentInstance = instance;
}
function mountComponent(vnode, container, anchor) {
const instance = {
state,
props: shallowReactive(props),
isMounted: false,
subTree: null,
mounted: [],//在组件实例中添加mounted数组, 用来储存通过onMounted函数注册的生命周期钩子函数
}
setCurrentInstance(instance); // setup之前
const setupResult = setup(shallowReadonly(vnode.props), setupContext);
setCurrentInstance(null);
}
注意点: setCurrentInstance在是setup之前调用, setup之后重置的, 为什么是setup,因为onMounted函数只能在setup中调用
function onMounted(fn) {
if(currentInstance) {
currentInstance.mounted.push(fn);
} else {
console.error("onMounted 函数只能在setup中调用");
}
}
effect(() => {
let subTree = render.call(renderContext, state); // render函数内部可以通过this访问组件自身状态数据
if(!instance.isMounted) {
// 省略代码
patch(null, subTree, container, anchor);
instance.isMounted = true;
instance.mounted && instance.mounted.forEach(hook => hook.call(renderContext))
} else {
// 省略代码
}
instance.subTree = subTree
}, {
scheduler: queueJob,
})
不完全的代码如下
const { effect, ref, reactive, shallowReactive} = VueReactivity;
const Text = Symbol();
const Comment = Symbol();
const Fragment = Symbol();
function createRenderer(options) {
const{ createElement, setElementText, insert, patchProps, unmount, setText, createText } = options;
function mountElement(vnode, container, anchor) {
const el = vnode.el = createElement(vnode.type);
if(typeof vnode.children === "string") {
setElementText(el, vnode.children)
} else if(Array.isArray(vnode.children)) {
vnode.children.forEach(child => {
patch(null, child, el, anchor)
})
}
if(vnode.props) {
for(let key in vnode.props) {
patchProps(el, key, null, vnode.props[key])
}
}
insert(el, container);
}
function patchElement(n1, n2) {
const el = n2.el = n1.el;
const oldProps = n1.props;
const newProps = n1.props;
for(const key in newProps) {
if(newProps[key] !== oldProps[key]){
patchProps(el, key, oldProps[key], newProps[key])
}
}
for(const key in oldProps) {
if(!(key in newProps)) {
patchProps(el, key, oldProps[key], null)
}
}
patchChildren(n1, n2, el)
}
function patchChildren(n1, n2, container) {
if(typeof n2.children === "string"){ // 1、新节点是文本节点, 1.1、没有节点 直接设置 1.2、文本节点 直接设置 1.3、数组节点 卸载新增
if(Array.isArray(n1.children)){
n1.children.forEach(child => unmount(child))
}
setElementText(container, n2.children);
} else if(Array.isArray(n2.children)) { // 2、新节点是数组节点
if(Array.isArray(n1.children)) { // 2.1、节点是数组节点 // 这里是diff算法, 暂时全部卸载后再更新
n1.children.forEach(c => unmount(c));
n2.children.forEach(c => patch(null, c, container))
} else { // 2.2、节点为空 不处理后挂载,2.3、文本节点 设置为空后挂载
setElementText(container, "");
n2.children.forEach(c => patch(null, c, container))
}
} else { // 3、新节点为空, 3.1、没有节点 不用处理 3.2、文本节点 设置为空, 3.3、数组节点 直接卸载
if(Array.isArray(n1.children)) {
n1.children.forEach(child => unmount(child))
} else if(typeof n1.children === "string") {
setElementText(container, "");
}
}
}
let isFlushing = false;
let queue = new Set();
let p = Promise.resolve();
function queueJob(job) {
queue.add(job)
if(!isFlushing) {
isFlushing = true;
p.then(() => {
try {
queue.forEach(job => job())
} finally {
isFlushing = false;
queue.clear() // 任务执行完成后需要清除
}
})
}
}
function resolveProps(option, propsData) {
let props = {};
let attrs = {};
for(let key in option) {
if(key in propsData || key.startsWith("on") {
props[key] = propsData;
} else {
attrs[key] = option[key];
}
}
return [props, attrs]
}
let currentInstance = null;
function setCurrentInstance(instance) {
currentInstance = instance;
}
function onMounted(fn) {
if(currentInstance) {
currentInstance.mounted.push(fn);
} else {
console.error("onMounted 函数只能在setup中调用");
}
}
function mountComponent(vnode, container, anchor) {
const componentOptions = vnode.type;
const { setup, render, data, beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, props: propsOption} = componentOptions
beforeCreate && beforeCreate();
let state = data ? reactive(data()) : null;
let [ props, attrs ] = resolveProps(propsOption, vnode.props);
const instance = {
state,
props: shallowReactive(props),
isMounted: false,
subTree: null,
mounted: [],
}
function emit(event, ...payload) {
let eventName = `on${event[0].toUpperCase()}${event.slice(0)}`; // change => onChange
const handler = props[eventName];
if(handler) {
handler(...payload);
} else {
console.log("事件不存在")
}
}
const setupContext = {attrs, emit};
setCurrentInstance(instance);
const setupResult = setup(shallowReadonly(vnode.props), setupContext); // 参数1: 只读的props,防止被更改, 参数2: 上下文,包括 slot, emit , attrs, expose
setCurrentInstance(null);
let setupState = null
if(typeof setupResult === "function") {
// 如果setup返回的值是函数,则如果存在render则报告冲突,将该函数作为render函数使用
if(render) console.error("setup 函数返回渲染函数, render函数将被忽略");
} else {
// 如果setup返回的值不是函数,则作为数据状态赋值给setupState
setupState = setupResult;
}
vnode.component = instance;
const renderContext = new Proxy(instance, {
get(target, p, receiver) {
const {state, props} = target;
if(state && p in state) {
return state[p]
} else if(p in props) {
return props[p]
} else if(setupState && p in setup) {
return setupState[p] // 渲染上下文增加对setupState的支持
} else {
console.log("不存在")
}
},
set(target, p, value, receiver) {
const {state, props} = target;
if(state && p in state) {
state[p] = value;
} else if(p in props) {
console.log("props is readonly")
} else if (setupState && p in setupState){
setupState[p] = value; // 渲染上下文增加对setupState的支持
} else {
console.log("不存在");
}
}
});
created && created();
effect(() => {
let subTree = render.call(renderContext, state); // render函数内部可以通过this访问组件自身状态数据
if(!instance.isMounted) {
beforeMount && beforeMount()
patch(null, subTree, container, anchor);
instance.isMounted = true;
instance.mounted && instance.mounted.forEach(hook => hook.call(renderContext))
} else {
beforeUpdate && beforeUpdate()
patch(instance.subTree, subTree, container, anchor);
updated && updated();
}
instance.subTree = subTree
}, {
scheduler: queueJob,
})
}
function hasPropsChanged(prevProps, nextProps) {
const prevKey = Object.keys(prevProps).length;
const nextKey = Object.keys(nextProps).length;
if(prevKey !== nextKey) return true;
for(let key in prevKey) {
if(nextKey[key] !== prevKey[key]) return true;
}
return false;
}
function patchComponent(n1, n2, container) {
const instance = n2.component = n1.component; // 获取组件实例, 并且给新的vnode绑定实例
const {props} = instance; // 获取当前的props;
if(hasPropsChanged(n1.props, n2.props)) { // 检测props是否发生变化, 如果没有变化则不需要更新
let [nextProps] = resolveProps(n2.type.props, n2.props);
// 更新props
for(let key in nextProps) {
props[key] = nextProps[key];
}
// 删除不要的props
for(let key in props) {
if(!(key in nextProps)) delete props[key];
}
}
}
// n1旧vnode n2新vnode, container容器
function patch(n1, n2, container, anchor) {
if(n1 && n1.type !== n2.type) {
unmount(n1);
n1 = null;
}
let { type } = n2;
if(typeof type === "string") { // 节点是普通标签元素
if(!n1) {
mountElement(n2, container, anchor); //挂载节点
} else {
patchElement(n1, n2); // 更新节点
}
} else if(typeof type === "object"){ // 节点是组件
if(!n1) {
mountComponent(n2, container, anchor)
} else {
patchComponent()
}
} else if(type === Text){
// 前面有判断,能进入到这个判断分支的, 要不节点为空, 要不同为Text节点
if(!n1) {
const el = n2.el = createText(n2.children);
insert(el, container, null);
} else {
const el = n2.el = n1.el;
if(n1.children !== n2.children) {
setText(el, n2.children)
}
}
} else if(type === Fragment) {
if(!n1) {
n2.children.forEach(c => patch(null, c, container))
} else {
patchChildren(n1, n2, container)
}
} else {
// 省略了其他类型的vnode
}
}
function render(vnode, container) {
if(vnode){
// 打补丁(挂载也是一种特殊的打补丁)
patch(container._vnode, vnode, container)
} else {
if(container._vnode) {
unmount(container._vnode);
}
}
container._vnode = vnode;
}
return {
render,
}
}
function createElement(tag){
return document.createElement(tag);
}
function setElementText(el, text){
el.textContent = text;
}
function insert(el, parent, anchor = null) {
parent.insertBefore(el, anchor) // parent父节点 el需要插入的节点 anchor插入时需要插入在这个节点前面
}
function patchProps(el,key, oldValue, newValue) {
// 暂时放在这里
function shouldSetAsProps(el, key, value) {
if(el.tagName === "INPUT" && key === "form") return false;
return key in el;
}
if(/^on/.test(key)) {
const name = key.slice(2).toLowerCase();
let invokers = el._vei || (el._vei = {});
let invoker = invokers[name];
if(newValue) {
if(!invoker) {
invoker = el._vei = function(e) {
if(e.timeStamp < invoker.attached) return;
if(Array.isArray(invoker.value)) {
invoker.value.forEach(fn => fn(e))
} else {
invoker.value(e);
}
}
invoker.value = newValue;
invoker.attached = performance.now();
el.addEventListener(name, invoker) ; // 在之前没有绑定过数据的情况下进行数据的监听
} else {
invoker.value = newValue;
}
// 为什么不在这里添加addEventListener, 因为每监听一次, 就多一次事件触发
} else if(invoker) {
el.removeEventListener(name, invoker)
}
} else if(key === "class") {
el.className = newValue || ""
} else if(shouldSetAsProps(el, key, newValue)){
let type = typeof el[key];
if(type === "boolean" && newValue === "") {
el[key] = true;
} else {
el[key] = newValue;
}
} else {
el.setAttribute(key, newValue)
}
}
function unmount(vnode) {
if(vnode.type === Fragment) {
vnode.children.forEach(c => unmount(c))
}
const parent = vnode.el.parent;
if(parent) parent.removeChild(vnode.el);
}
function createText(text) {
return document.createTextNode(text)
}
function setText(el, text) {
el.nodeValue = text;
}
const renderer = createRenderer({
createElement,
setElementText,
insert,
patchProps,
createText,
setText
});
const Comp = {
setup(props, setupContext) {
let value = props.data;
let {attrs, slot, emit, expose} = setupContext;
},
}