react immutable

229 阅读4分钟

JavaScript Immutable

Object.is()

react的更新机制是通过Object.is()判断是否有状态变更,决定是否更新UI

Object.is(value1, value2)什么时候返回ture?

Object.is() 确定两个值是否为相同值。如果以下其中一项成立,则两个值相同:

  • 都是 undefined
  • 都是 null
  • 都是 true 或者都是 false
  • 都是长度相同、字符相同、顺序相同的字符串
  • 都是相同的对象(意味着两个值都引用了内存中的同一对象)
  • 都是 BigInt 且具有相同的数值
  • 都是 symbol 且引用相同的 symbol 值
  • 都是数字且
    • 都是 +0
    • 都是 -0
    • 都是 NaN
    • 都有相同的值,非零且都不是 NaN Object.is() 与 == 运算符并不等价。== 运算符在测试相等性之前,会对两个操作数进行类型转换(如果它们不是相同的类型),这可能会导致一些非预期的行为,例如 "" == false 的结果是 true,但是 Object.is() 不会对其操作数进行类型转换。 Object.is() 也等价于 === 运算符。Object.is() 和 === 之间的唯一区别在于它们处理带符号的 0 和 NaN 值的时候。=== 运算符(和 == 运算符)将数值 -0 和 +0 视为相等,但是会将 NaN 视为彼此不相等。

const无法实现引用类型数据不可变

基本类型

const a = 'word';
a = 'newWord'; // 编译报错

注:用const定义的基本类型,修改时会触发运行时报错

引用类型

const定义的ObjectArray类型仅仅实现了引用不可变,里面属性是mutable

Object

// 对象
const user = {}
user.name = 'brad' // This is a mutation because we're mutating the object
// 下面修改直接编译报错,因为修改了user的引用
user = 9 // not allowed, re-assignment of a constant to a new value
user = { name: 'brad' } // not allowed, re-assignment of a constant to a new 

Array

const users = [
  { id: 1, name: 'michael' },
  { id: 2, name: 'brad' }, // <-- let's remove
  { id: 3, name: 'ryan' },
]
const index = users.findIndex((u) => u.id === 2) // find brad's index
// Mutation: The splice method can be used to add or remove from an array
// Now the users array just has michael and ryan in it.
// 修改了原有的数据
users.splice(index, 1)

怎么理解不可变?

这里说的数据不可变是数据被定义后就不可以修改。

  const obj = { a: 1 };
  Object.freeze(obj); // Object,freeze
  obj.a = 2; // 报错

如何修改数据

基本类型

const val = 'word';
const newVal = 'newWord';

直接赋值

引用类型

1.复制原数据
2.修改数据
3.指向新的引用

Array
// We still need to find the index
const index = users.findIndex((u) => u.id === 2)
// Here's the immutable part:
// 1. Make a copy of the original
// 2. Make changes to the copy
// 3. Replace the original with the copy
const newArray = [...users.slice(0, index), ...users.slice(index + 1)]
Object
const person = { name: 'brad', occupation: 'web' }
changeOccupation(person, 'web developer') // <-- Let's write this function below:

function changeOccupation(person, occupation) {
  // If we did this, it would be a mutation:
  person.occupation = occupation
  return person
}

// But if we did any of these, they would be immutable:

// Immutable Option One
function changeOccupation(person, occupation) {
  return Object.assign({}, person, { occupation: occupation })
}

// Immutable Option Two (Newer Way)
function changeOccupation(person, occupation) {
  return { ...person, occupation: occupation }
}

基本类型

注:基本类型在javascript设计上就是不可变

MDN Primitive

所有原始值都是不可变的,即它们的值不能被修改。重要的是不要将原始值本身与分配了原始值的变量混淆。变量可能会被重新赋予一个新值,但存在的值不能像数组、对象以及函数那样被修改。语言不提供改变原始值的工具方法。

React Immutable

怎么理解React Immutable

定义state时候都通过关键字const,语义上实现不可变

function App() {
  const [count, setCount] = useState(0)
  const [obj, setObj] = useState({ a: 1, b: 2 })
  return (
    <div>
      <button onClick={() => {
        count = 2; // 运行时会报错
        obj.a = 2; // 运行时不会报错,破坏了数据不可变原则
        setCount(count + 1)
        setObj({ ...obj, a: obj.a + 1 })
      }}>update count</button>
      {count}
    </div>
  )
}

react基本类型

不可变在定义时就实现了,禁止修改基础类型值;如果修改,就会报与形式报错TypeError: Assignment to constant variable.

react引用类型

代码第8行修改了obj破坏了react数据不可变原则

这里你会有个疑问,react实现状态更新不是依赖数据变更吗?

答案是肯定的;更严谨一点表达是状态变更(state change)

App函数在组件挂载时会被执行,状态更新时也被执行;每次执行都可以理解为一个闭包;因此在App函数内都可以实现数据不可变,不允许再修改count、obj。

一个例子渐进式解析

1.直接改变原数据

function App() {
  const [users, setUsers] = useState([
    { id: 1, name: 'michael' },
    { id: 2, name: 'brad' },
    { id: 3, name: 'ryan' },
  ])

  function addUser(newUser) {
    // Try push (mutation)
    users.push(newUser)
  }

  return (
    <div>
      <AddUserForm onSubmit={addUser} />
      <ShowUsers users={users} />
    </div>
  )
}

没有调用状态更新方法

2.通过setState更新

function addUser(newUser) {
  users.push(newUser)
  // Now let's set state with users since it's
  // now a bigger array. Hopefully this causes
  // a re-render 🤞
  setUsers(users)
}

根据Object.is()判断,组件不会更新

3.通过不可变实现更新

以下例子没有改变原始user,通过新的指针存储新的user state

function addUser(newUser) {
  // This works (new array)
  setUsers([...users, newUser])
}

function addUser(newUser) {
  // This also works
  setUsers(users.concat(newUser))
}

调用更新状态方法,触发更新调度;Object.is()返回false,触发组件更新。

React Immutable最佳实践

immer

参考

State in React is Immutable