Vue渲染函数该如何使用?有哪些需要注意的地方?

2,539次阅读
没有评论

共计 3021 个字符,预计需要花费 8 分钟才能阅读完成。

场景分析

Vue 的模板语法适用于绝大部分的需求场景(模板最终会被编译为渲染函数),在绝大多数情况下,Vue 推荐使用模板语法来创建应用。然而在某些使用场景下,我们真的需要用到 JavaScript 完全的编程能力,举例如下:

1. 不确定层级的菜单

假设设计一个开源的后台管理系统,侧边栏菜单需要根据路由自动生成菜单,由于系统可能会被用于不同的功能需求。所以路由的层级、数量都是不确定的。

如果通过模板语法来写,假设路由最多只有三层,我们当然可以在模板内通过 if 加循环来适配所有需求场景,但是实际场景并非如此。

2. 组织架构

组织架构的常见实现就是 Tree 组件,Tree 组件的特点之一就是没有确定数量的数据、没有确定数量的层级。此处可以思考一下,如果使用模板语法该如何去实现这样的一个功能组件?

3. 总结分析

通过渲染函数,对于以上的例子我们完全可以通过递归满足生成任意层级、数量的菜单栏、Tree 分支。(此处不作具体展开)。

我们可以先推出结论:模板适用于“组件结构是确定的”这种需求场景,此处的确定可以简单理解为:“嵌套的层级是确定的”,在这种情况下模板语法比渲染函数更加简单易用。但是当组件结构层级不确定时,渲染函数显然更加合适。

使用渲染函数

1. 选项式 API

// 选项式 API
export default {props: ['message'],
  render() {
    return [// 
h('div', this.$slots.default()), //
h( 'div', this.$slots.footer({text: this.message}) ) ] } }

2. 组合式 API

export default {props: ['message'],
  setup(props, { slots}) {return () => [
      // 默认插槽:// 
h('div', slots.default()), // 具名插槽://
h( 'div', slots.footer({text: props.message}) ) ] } }

使用总结

1.vNode 必须唯一

同一个 vNode 对象,不能被多次用于渲染函数,必须保证 vNode 的唯一性;

2.v-model 需要自己实现

v-model 语法糖会被拆分为 modelValue 和 onUpdate:modelValue 事件,在渲染函数中需要我们自己实现双向绑定的逻辑处理;

3. 传递插槽

// 单个默认插槽
h(MyComponent, () => 'hello')

// 具名插槽
// 注意 `null` 是必需的
// 以避免 slot 对象被当成 prop 处理
h(MyComponent, null, {default: () => 'default slot',
    foo: () => h('div', 'foo'),
    bar: () => [h('span', 'one'), h('span', 'two')]
})

4. 渲染子元素

对于组件的子元素,每一个非纯字符串的子元素都应该通过传递一个返回 Vnode 的函数来指定,函数返回值可以是 vNode、Vnode 数组、插槽对象表示的 vNode

h(FormItem,null,()=>{default:h("div")}) // 对象
h(FormItem,null,()=>h("div"))  // 单个 VNode
h(FormItem,null,()=>[h("div")]) // 数组

需要注意的是如果渲染普通的 html 标签时,不能返回对象格式(会导致无法渲染,并且不报错);

// 这样子不会被渲染,估计是普通的 html 没有插槽的概念
return h("div",null,{default:()=>h(Item)}
// 这样可以
return h("div",null,()=>[h(Item)])
return h("div",null,()=>h(Item))

5. 渲染函数的依赖收集

假设组件某属性需要的是 Array,通过 Ref 包装一个数组,直接把这个 Ref 传递给组件,组件会报错提示需要的是数组,得到的是对象,说明渲染函数中 ref 对象不会转换成原数组,然后保持响应式传递给被渲染的组件。

这个过程需要我们自己完成(触发渲染函数的依赖收集机制)。测试如下:

//item 是一个 ref,这样会触发依赖收集保持响应式
h("input",{value:item.value});

// 这样就不会
let attr={value:item.value}
h("input",attr);

// 这样才可以
let attr={value:item.value}
h("input",Object.assign({},attr));

经过测试,在渲染函数内被调用的 ref,reactive 对象都会收集依赖保持响应式,在渲染函数调用前定义 let attr={value:item.value},在这个过程没有依赖收集,value 被赋值的是一个普通的值,所以不会具有响应性(直接传递 ref 对象,会导致类型错误)。

6. 这样也会收集依赖

 () => h(components[item.type],
         Object.assign({value: props.data[item.key] }, 
         item.attr,
         options.data.length == 0 ? {} : {options: options.data}))
 )));

7. 依赖收集

/* 这样会收集,options 改变会进行响应 */
Object.assign(
        {value: props.data[item.key]
        }, item.attr,
        isRef(item.attr.options) ? {options: item.attr.options.value} : {},
    options.data.length == 0 ? {} : {options: options.data}))

/* 这样 options 改变不会进行响应 */
Object.assign(
       {value: props.data[item.key]
       },
        Object.assign(item.attr, isRef(item.attr.options) ? {options: item.attr.options.value} : {}),
        options.data.length == 0 ? {} : {options: options.data}))

其它的知识

1.reactive

reactive() API 有两条限制:仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。

Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:

let a=reactice({b:{c:1}
})

a.b.c++; // 响应性保持

let c=a.b.c;
c++; // c 已经独立了,没有响应性

let c=a.b;
c.c++; // 还保持着引用,响应性存在

let d=a.b;
d={c:1};
d.c++; // 这就没了,因为 d 整个 Proxy 对象被替换了,变成没有代理的对象了。

2. 绑定事件

事件绑定和属性是一样的,只不过事件属性需要以 on 开始,例如 onUpdate:value,监听的就是 update:value 事件。

3. 渲染的时机

每次依赖更新的时候,都会重新调用渲染函数然后刷新 DOM,简单说就是 setup 只会运行一次,渲染函数每次刷新的时候都会调用。

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于2022-08-31发表,共计3021字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)