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 - 都有相同的值,非零且都不是
NaNObject.is()与==运算符并不等价。==运算符在测试相等性之前,会对两个操作数进行类型转换(如果它们不是相同的类型),这可能会导致一些非预期的行为,例如"" == false的结果是true,但是Object.is()不会对其操作数进行类型转换。Object.is()也不等价于===运算符。Object.is()和===之间的唯一区别在于它们处理带符号的 0 和NaN值的时候。===运算符(和==运算符)将数值-0和+0视为相等,但是会将NaN视为彼此不相等。
- 都是
const无法实现引用类型数据不可变
基本类型
const a = 'word';
a = 'newWord'; // 编译报错
注:用
const定义的基本类型,修改时会触发运行时报错
引用类型
const定义的Object、Array类型仅仅实现了引用不可变,里面属性是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,触发组件更新。