相关文章推荐
有人问罗翔: 在一个浪潮变化越来越汹涌的时代中,我们应该躬身入局还是保持距离呢?
罗翔老师回答道:
人生很渺小,就像浮萍一样,当浪潮来的时候,人是躲不了的,你只能顺势而为。换言之,人要接受一切的可开放性,但是无论在任何的情境中,要有自己的 定海神针 ,如果人的定海神针被拿去,那人一定是会随波逐流,只有拥有了自己的定见,才能够在所有的浪潮中,能够持一种开放的心态,去接受一切的变化。
然后我就在想, 前端领域 发展之迅速,我们可以接受新技术,当要有自己的定海神针,知道哪些技术是当下你需掌握的,明白自己的定位,而不是来什么马上学什么。

文章后附带 原理 ,在 四、v-modul双向绑定使用和原理吗?

一、存在问题

v-model 想绑定表达式 || 函数方法,发现控制台报错了,不允许这波操作。

下面我们分析存在该问题的原因和解决方法。

实战经验。

二、还原场景

有这样子的数组对象结构

const arr = [
  { value: 'a' }, { value: 'b' }
const item {
  a: 1,
  b: 2,
  __config__:{ required: false },

想循环arr数组,然后通过arr.value去找item里面的属性,然后绑定在v-model上。

理想效果是

arr我们有两个对象,就循环两个出来,通过arr[0].value去寻找item的属性。

<van-switch v-model="item[arr[0].value]" /> <van-switch v-model="item[arr[1].value]" />

于是,我们可以很顺利的😁写出遍历

v-for="(cell, cellKey) in arr" :key="cellKey" <van-switch v-model="item[cell.value]" /> </div>

前面只是为下面做铺垫,其实真实的场景会更复杂点。

数组对象结构是这样子的:

想通过arr.value去找item里面的属性,然后绑定在v-model上。

现在的arr数组的对象不再是一层了,而是多层,如:

{ value: '__config__.required' }

这样的结构怎么在template上遍历呢?

const arr = [
  { value: 'a' }, { value: '__config__.required' }
const item {
  a: 1,
  b: 2,
  __config__:{ required: false },

三、进行分析

3.1 直接赋值法

于是,有了第一个想法,就是arr数组写着:

{ value: '__config__.required' }

直接看v-for遍历出来是怎么样的。

v-for="(cell, cellKey) in arr" :key="cellKey" <van-switch v-model="item[cell.value]" /> </div>

他不是在__config__对象中去设置required,而是把__config__.required当做一个属性了。

v-model 不仅仅是语法糖,它还有副作用。

如果 v-model 绑定的是响应式对象上某个不存在的属性,那么 vue 会悄悄地增加这个属性,并让它响应式。

很明显,这个方法不行❌,只适用于item.a,不能支持多层item.__config__.required

再尝试另外一种方法🚶。

3.2 通过方法解析

我想在v-model绑定一个函数专门来让其value里面的'__config__.required'取出。

理想是通过一个方法,比如叫_get方法,这个方法可以让他处理成item['__config__']['required'],这样子我们就可以取到正确的值了。

* @description 实现 lodash 的_.get
* @param {Array/Object} source 目标数组/对象
* @param {String} path 获取对象的字符串路径
* @param {*} [defaultValue=undefined] 若值为空,则可传入默认值作为返回值
* @return {*} 
* @example
* const get1 = tool._get({ a: null }, "a.b.c", 3)       // output: 3
  const get2 = tool._get({ a: [{ b: 1 }] }, "a[0].b")   // output: 1
_get(source, path, defaultValue = undefined) {
 // a[3].b -> a.3.b -> [a,3,b]
 // path 中也可能是数组的路径,全部转化成 . 运算符并组成数组
 const paths = path.replace(/\[(\d+)\]/g, ".$1").split(".")
 let result = source
 for (const p of paths) {
   // 注意 null 与 undefined 取属性会报错,所以使用 Object 包装一下。
   result = Object(result)[p]
   if (result == undefined) {
     return defaultValue
 return result

然后在模板里面遍历,通过_get解析取值。

v-for="(cell, cellKey) in arr" :key="cellKey" <van-switch v-model="_get(item, cell.value)" /> </div> data(){ return { const arr = [ { value: 'a' }, { value: '__config__.required' } const item { a: 1, b: 2, __config__:{ required: false },

发现控制台报错了。

v-model‘ directives require the attribute value which is valid as LHS

📖中文翻译: “v-model”指令要求属性值必须与LHS一样有效

❓原因:v-model将始终把Vue实例的data视为数据真实的来源,你应该在组件的JavaScript里的data中声明初始值,这意味着你不能要求v-model一次观察多个变量。

通俗易懂的说:你不能在v-model绑定函数或者表达式。

看来,这个方法也❌行不通,继续探索解决方案🚶。

3.3 通过computed计算属性

v-model 的值只能是一个变量。

值得一提,v-model可以绑定计算属性的值,因为计算属性也是data数据真实的数据,是一个变量。

那我们就可以在computed计算属性里面去通过_get方法解析取值。

v-for="(cell, cellKey) in arr" :key="cellKey" <van-switch v-model="getItem" /> </div> computed:{ getItem(){ return _get(this.item, 写啥)

我们可以发现,写不下去了,如果没有v-for遍历,只是一个值的话,就可以。

但是,咱们这里的遍历的,我们不能在遍历的时候,传参数给计算属性getItem,传了就变成方法了,就会报错。

计算属性可以用,但在v-model不能传参。

<van-switch v-model="getItem"  />
// 我们不能写
<van-switch v-model="getItem(item, cell.value)"  />

看来,计算属性在我们这里是❌行不通了,但计算属性可以解决我们v-model不能去调用方法的问题。

计算属性还是可以很好的解决单个值写表达式的。

3.4 two value

我想到一个最傻的方案。

就是写两个value,然后判断一下,如下代码:

v-for="(cell, cellKey) in arr" :key="cellKey" <template v-if="cell.value1"> <van-switch v-model="item[cell.value][cell.value1]" /> </template> <template v-else> <van-switch v-model="item[cell.value]" /> </template> </div> data(){ return { const arr = [ { value: 'a' }, { value: '__config__', value1: 'required' } const item { a: 1, b: 2, __config__:{ required: false },

如果可以写表达式,里面就不需要写两条了,直接写:

<van-switch 
  v-model="cell.value1 ? item[cell.value][cell.value1] : item[cell.value]"  

当然,直接写个方法来解析获取__config__.required会更好。

3.5 父子组件 + 计算属性

有了以上的实践,我们可以来总结一下目前的情况:

可以使用计算属性写表达式。

但局限于,我们的场景是循环的,那我们使用计算属性的话需要传参,但一旦传参则认为是方法,v-model无法支持。

我们可以想到,可以创建一个新的组件,然后组件内接收每次循环的item,在新的组件里面写v-model绑定计算属性。

Perfect完美。

代码如下:

<div v-for="(cell, cellKey) in arr" :key="cellKey">
    <Item :item="item" :cell="cell" />
</div>
data(){
   return {
    const arr = [
      { value: 'a' }, { value: '__config__', value1: 'required' }
    const item {
      a: 1,
      b: 2,
      __config__:{ required: false },

子组件Item.vue

<van-switch v-model="itemVal" /> </div> <script> props:['item', 'cell'] computed:{ itemVal(){ return this.cell.value1 ? this.item[this.cell.value][this.cell.value1] : this.item[this.cell.value] </script>

于是,我们愉快的打开页面看下效果成功了没,发现控制台吐了块红色的错误。

Computed property "itemVal" was assigned to but it has no setter.

意思是:计算属性 itemVal被赋值了,但此它并未定义 set方法 。

要解决这个问题,首先要明确这个问题出现的原因。这个警告是由于Vue的计算属性内部没有set方法,即:计算属性不支持值的修改(只能针对data中的值进行计算)。

因为我们是v-model双向绑定,所以会触发到值的修改。

computed:{
    itemVal:{
      get(){
        return this.cell.value1 ? this.item[this.cell.value][this.cell.value1] : this.item[this.cell.value]
      set(v){
        if(this.cell.value1){
          this.item[this.cell.value][this.cell.value1] = v
        }else{
          this.item[this.cell.value] = v

如上面所示,只要手动给计算属性添加get和set方法的不同操作,这个警告就解决了。

那既然可以写方法了,我们可以利用上面的_get方法去解析,就不需要写两个value、value1了,写一个value: "__config__.required",通过_get去解析即可。

3.5.1 对象新增属性,v-model无法响应式?

我们在计算属性的set方法要注意。

我遇到过一个现象:有一个输入框,输入Dignity_呱的时候会触发set方法,但是当我失去焦点的时候,刚刚输入的值被清空了。

这是怎么回事?

代码如下:

<van-field v-model={this.itemVal} ></van-field>
computed:{
    itemVal:{
      get(v){
        return this.item[this.cell.value]
      set(v){
          this.item[this.cell.value] = v
item:{
  id: 123
cell:{
  value: 'maxLength'

调试发现,van-field输入的时候确实是有进set方法的。

我们可以发现,item初始化的时候并没有maxLength属性,所以对于对象中无该属性的,无法响应式。触发set方法的时候,console.log的时候,item已经有maxLength的属性和值了,已经保存了,但是在视图上是还没有同步过来。

这就是vue2使用Object.defineProperty的小缺陷。

那怎么解决呢?

vue通过$set让其字段具有响应式。

代码改为:

computed:{
    itemVal:{
      get(v){
        return this.item[this.cell.value]
      set(v){
          // 可防止item无该属性,而导致无法响应式
          this.$set(this.item, this.cell.value, v)  

所以我们知道Object.defineProperty之后,能马上定位到问题。

四、v-modul双向绑定使用和原理吗?

既然讲到v-model,那来道面试题讲解一下。

说一下v-modul双向绑定使用和原理吗?

我个人觉得可以从以下五个方面去回答这个面试题。

给出双向绑定的定义

双向绑定带来的好处

在哪使用双向绑定

使用方式、使用细节、vue3变化

原理实现描述

有没有发现,现在Vue2的面试题,我们一般都会聊到Vue3做了哪些优化和改进,回答问题不能太过于片面。

第一:讲定义

vue中双向绑定是一个指令v-model,可以绑定一个响应式数据到视图,同时视图中变化能改变该值。

第二:讲好处

v-model是语法糖,默认情况下相当于:value@input。使用v-model可以减少大量繁琐的事件处理代码,提高开发效率。

第三:场景

通常在表单项上使用v-model,还可以在自定义组件上使用,表示某个值的输入和输出控制。

可以通过model属性的propevent属性来进行自定义。原生的v-model,会根据标签的不同生成不同的事件和属性

第四:使用方式、细节、Vue3变化

通过<input v-model="xxx">的方式将xxx的值绑定到表单元素value上;

对于checkbox,可以使用true-value和false-value指定特殊的值;

对于radio可以使用value指定特殊的值;

对于select可以通过options元素的value设置特殊的值;

还可以结合.lazy,.number,.trim对v-mode的行为做进一步限定;

v-model用在自定义组件上时又会有很大不同,vue3中它类似于sync修饰符,最终展开的结果是modelValue属性和update:modelValue事件;

vue3中我们甚至可以用参数形式指定多个不同的绑定,例如v-model:foov-model:bar,非常强大!

第五:原理

v-model是一个指令,它的神奇魔法实际上是vue的编译器完成的。

我做过测试,包含v-model的模板,转换为渲染函数之后,实际上还是value属性的绑定以及input事件监听,事件回调函数中会做相应变量更新操作。

编译器根据表单元素的不同会展开不同的DOM属性和事件对,比如text类型的input和textarea会展开为value和input事件;checkboxradio类型的input会展开为checked和change事件;select用value作为属性,用change作为事件。

input输入框监听input事件修改值,通过Object.defineProperty劫持数据发生的改变,如果数据发生改变了(在set进行赋值的),触发update方法进行更新节点的内容({{str}}),从而实现了数据双向绑定的原理。

巧妙的利用计算属性可以是变量,来为v-model进行绑定。

这是在开发过程中的一些思路、一些尝试点。

代码总是一点一点优化,有时候先把功能点走通,再花点时间看看,能不能优化,精而再精🦀。

这也是在实际项目中真实遇到的场景。

👍 如果对您有帮助,您的点赞是我前进的润滑剂。

靓仔,说一下keep-alive缓存组件后怎么更新及原理?

面试官问我watch和computed的区别以及选择?

面试官问我new Vue阶段做了什么?

前端仔,快把dist部署到Nginx上

多图详解,一次性啃懂原型链(上万字)

Vue-Cli3搭建组件库

Vue实现动态路由(和面试官吹项目亮点)

项目中你不知道的Axios骚操作(手写核心原理、兼容性)

VuePress搭建项目组件文档

juejin.cn/post/716678…

 
推荐文章