1. 引子
以前的深复制方案总有各种各样的问题:
- ...展开运算符复制:甚至不能称为深复制,因为只是比浅复制多了一层而已。
- json.stringify/json.parse:弊端
- 手写递归:性能极差,需要把所有节点都遍历
因此,需要一种更为完善的深拷贝方案:引入immutable搭配react使用。
每次修改一个Immutable对象时都会创建一个新的不可变的对象,在新对象上操作并不会影响到原对象的数据。
Immutable实现的原理是Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免deepCopy把所有节点都复制一遍带来的性能损耗,Immutable使用了Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点。
2. 基本操作
通过Map或者fromJS转换对象
import {Map} from 'immutable'
let obj = {
name : 'react',
age: 100
}
// 将对象转换为immutable对象
let oldImmuObj = Map(obj);
// set设置属性
let newImmuObj = oldImmuObj.set('name' , 'vue')
// get获取属性值
console.log(oldImmuObj.get('name') , newImmuObj.get('name'))
// toJS转换回普通对象
console.log(oldImmuObj.toJS() , newImmuObj.toJS())
结合react的state,有两种写法:
直接在state阶段就Map转换,那么在渲染时就要通过get去获取
export default class App extends Component {
state = {
info:Map({
name:"react",
age:100
})
}
render() {
return (
<div>
<button onClick={()=>{
this.setState({
info:this.state.info.set("name","xiaoming").set("age",18)
})
}}>click</button>
{this.state.info.get("name")}--
{this.state.info.get("age")}
</div>
)
}
}
或者在点击按钮时才在事件中进行转化
export default class App extends Component {
state = {
info: {
name: "react",
age: 100
}
}
render() {
return (
<div>
<button onClick={() => {
var oldImmu = Map(this.state.info)
var newImmu = oldImmu.set("name", "xiaoming").set("age", 18)
this.setState({
info: newImmu.toJS()
})
}}>click</button>
{this.state.info.name}--
{this.state.info.age}
</div>
)
}
}
通过List转换数组
var arr = List([1, 2, 3])
var arr2 = arr.push(4) //不会影响老的对象结构
var arr3 = arr2.concat([5, 6, 7])
console.log(arr.toJS(), arr2.toJS(), arr3.toJS())
3. 应用于shouldComponentUpdate
假设父组件只给子组件传了属性a,但是父组件修改自己的状态b所导致的重新渲染也会导致子组件的重新渲染,此时子组件可以通过shouldComponentUpdate来阻止渲染。
之前scu是通过json.stringify()的方式判断新老对象是否相同,但是这种判断方式会有各种弊端,比如对象里有undefined等。这时候就可以通过immutable实现:
export default class App extends Component {
// 父组件状态 这里要用两次Map包住对象
state = {
info: Map({
name: "react",
filter: Map({
text: ""
})
})
}
render() {
return (
<div>
{/* 修改状态 引发re-render */}
<button onClick={() => {
this.setState({
info: this.state.info.set("name", "xiaoming")
})
}}>click</button>
{this.state.info.get("name")}
{/* 将filter这个immutable对象传给孩子 */}
<Child filter={this.state.info.get("filter")} />
</div>
)
}
}
class Child extends Component {
shouldComponentUpdate(nextProps, nextState) {
// 这里此时以直接用===来判断 而无需再通过json.stringify了
if (this.props.filter === nextProps.filter) {
return false
}
return true
}
render() {
return <div>
child
</div>
}
}
4. 个人信息修改demo
首先写出一个比较复杂的state,既包含对象又包含数组,**为了保证老state的可用,需要复制一个新的state用于操作。**此时就只有immutable比较合适。
state = {
info: Map({
name: "react",
location: Map({
province: "广东",
city: "广州"
}),
favor: List(["读书", "看报", "写代码"])
})
}
具体主要就是先通过fromJS转换为对象,再通过setIn,updateIn进行修改属性,更新数组等操作。
render() {
return (
<div>
<h1>个人信息修改</h1>
<button onClick={() => {
this.setState({
// 通过setIn修改属性
info: this.state.info.setIn(["name"], "vue").setIn(["location", "city"], "深圳")
})
}}>修改</button>
<div>
{this.state.info.get("name")}-
{this.state.info.get("location").get("province")}-
{this.state.info.get("location").get("city")}
{
this.state.info.get("favor").map((item, index) =>
<li key={item}>{item}
<button onClick={() => {
this.setState({
// 通过updateIn修改数组 接受回调函数返回一个新的list
info: this.state.info.updateIn(["favor"], (list) => list.splice(index, 1))
})
}}>del</button>
</li>
)
}
</div>
</div>
)
}