Skip to content

Vue.js 中的副作用

Published:

前言

副作用并不是绝对不好的,因为它们在某些情况下可能会有用,比如在 setTimeout()​ 函数中。但我们需要努力确保我们知道这些副作用。

什么是副作用

副作用 (Side Effect) 是指当一个函数改变或修改不在其作用域内的数据时。一个简单的例子是一个接收一个数组作为参数的函数,在函数内部修改了该数组的数据。

const numberList = [1, 2, 3, 4, 5, 6];

function addItem(item) {
  return item.push(item.length - 1 + 1);
}

addItem(numberList);
console.log(numberList);

在上面的例子中,我们发现在调用 addItem​ 时 numberList​ 被修改了。

Vue 中的副作用

例子1:计算属性中的副作用,一个例子如下:

<template>
<div>
	Unsorted Data: {{ languages }}
</div>
<div>
	Sorted Data: {{ sortLanguages }}
</div>
</template>

<script>
export default {
  name: ‘languages-list’,
  data() {
    return {
       languages: [
           {
              name: "React",
           },
           {
              name: "Vue",
           },
           {
              name: "Angular",
           },
           {
              name: "Svelte",
           }
        ]
     },
  },
  computed: {
    sortLanguages() {
      return this.languages.sort( ( a, b ) => {
        if ( a.name < b.name )  { return -1; }
        if ( a.name > b.name )  { return 1; }
        return 0;
      } )
    }
  }
}
</script>

从上面的例子中,我们可以看到未排序的数据和排序后的数据是相同的。当引用 sortedLanguages​ 时,languages​ 数据发生了变化。如果不正确处理,这是一个可能导致出现意外行为的副作用。

例子2:访问 getter 时的副作用

试着在访问和分配 getter 数据时创建一个副作用的例子。

state: {
  languages: [
    {
      name: "React",
    },
    {
      name: "Vue",
    },
    {
      name: "Angular",
    },
    {
      name: "Svelte",
    }
  ]
},
getters: {
  getLanguages: function (state) {
    return state.languages;
  }
},
actions: {
  sortLanguages: function (context) {
    const languages = JSON.parse(JSON.stringify(context.getters.getLanguages()));
    return languages.sort((a, b) => {
      if (a.name < b.name)  { return -1; }
      if (a.name > b.name)  { return 1; }
      return 0;
    });
  }
}

在这个例子中,我们可以看到当调用(dispatch)sortLanguages​ 动作时,存储会得到更新。这是一个副作用,可能导致应用程序出现意外行为,因为languages​ 状态被改变。

如何解决这些副作用呢?

  computed: {
    sortLanguages() {
      const languagesClone = this.languages.slice();
      return languagesClone.sort( ( a, b ) => {
        if ( a.name < b.name )  { return -1; }
        if ( a.name > b.name )  { return 1; }
        return 0;
      } )
    }
  }

当我们使用.slice()​方法对语言数组进行浅克隆时,它会返回一个原数组部分的浅拷贝,生成一个新的数组对象。我们这样做是为了防止对数据的修改。

  computed: {
    sortLanguages() {
      const languagesClone = json.parse( json.stringify( this.languages ) );
      return languagesClone.sort( ( a, b ) => {
        if ( a.name < b.name )  { return -1; }
        if ( a.name > b.name )  { return 1; }
        return 0;
      } )
    }
  }

要进行深拷贝,我们可以简单地使用 JSON.parse(JSON.stringify())​ 来操作我们的数据。

JSON.stringify​ 将数据转换为字符串,从而消除了数据中的引用。JSON.parse​ 将字符串转换回对象数组。

在 Vue.js 中,修改存储中的响应式属性的副作用可能会隐藏起来,导致难以追踪和调试的意外错误。