React Redux 组件更新/渲染 原理 connect之mapStateToProps 看这篇就够了!比中文文档好用!
本文深入浅出mapStateToProps
,解答:
- 为什么修改state,组件未渲染/更新?
- 如何从新旧state判断更新的值、未更新的值,从而决定是否re-render?
Redux中,
state
作为单一的数据源,众所周知,每次更新state
都要通过return { ...state, others }
来返回一个新的state
,但是它是怎么来判断一些组件到底要不要re-render(刷新、重渲染)呢?尤其是当state
层次很深的时候,会有效率问题、该刷新时不刷新的问题吗?
其实关键在于这个connect()
函数的第一个参数:mapStateToProps
。下面举个例子:
/* 假设state的结构如下: state = { user: {username: 'root', name: '根'}; globals: {showMenu: true, showModal: false}; }; */ function mapStateToProps(state) { return { user: state.user, // 比如常见的获取用户信息 showMenu: state.globals.showMenu, // 比如state层次有多层,可以直接获取到那个深层的数据 }; } export default connect(mapStateToProps)(MyComponent);
众所周知,mapStateToProps
必须是个纯函数,只要有相同的输入,一定会有相同的输出,不会有任何副作用,没有异步操作。输入是state
,输出是一个对象,这个对象会变为被connect
组件的props
。
其实,这个函数通常是选取了state
的一个子集,通过props
映射到了组件上。如果这个子集更新了,那么组件就会re-render。具体原理、过程如下:
原理
当state
更新时(即nextState !== state
,注意这里用===
来比较,所以每次更新state
需要用文章开头的方式来更新),
react-redux会调用所有的mapStateToProps
函数(所以每个mapStateToProps
函数应该很快能运算结束,不然会成为应用性能瓶颈),
针对每次调用mapStateToProps
,检查函数的结果(是个对象)的每个key的value跟上一次对应的value是否一致(这里也是用===
来比较!)如果所有value都一致,不会渲染这个组件;如果有一个value变了,就重新渲染。
PS. 所以react-redux中对mapStateToProps
的结果的比较是浅比较,即会遍历所有key来判断是否相等,对每个key的value的判断方法是===
。所以,要搞清楚“===
”、“浅比较”、“深比较”的差别。
举例说明
用上面举例提到的mapStateToProps
函数。(所以每次会比较返回值key是user
的value和key是showMenu
的value是否相等)
function mapStateToProps(state) { return { user: state.user, showMenu: state.globals.showMenu, }; }
假设有初始state
如下:
state = { user: {username: 'root', name: '根'}; globals: {showMenu: true, showModal: false}; };
案例1:一个正确示范
比如有一个某个操作dispatch
了一个action
,返回新的state
的代码如下:
return { ...state, user: {username: 'foo', name: 'bar'} };
通过对比,发现showMenu
还是原来的showMenu
,但是user
变成了新的对象,所以,重新渲染!
案例2:一个正确示范,可看出redux对性能的优化,reducer要用浅拷贝!
比如有一个某个操作dispatch
了一个action
,返回新的state
的代码如下:
const globals = {...state.globals, showModal: true}; return { ...state, globals };
通过对比,发现showMenu
还是原来的showMenu
(因为都是true
,发现相等),而且user
也是原来的user
,所以,不会重新渲染!
注意,这里的state.globals
已经不是原来的state.globals
了,但是函数返回的对象中:key是showMenu
的value没变,所以不会重新渲染。
思考1:如果组件中用到的是某个state
的某个部分的值,mapStateToProps
函数一定要尽可能细化到它,这有助于优化!
思考2:写reducer更新state
时,浅拷贝就够了,千万不要深拷贝!
// 如果有这样的状态 state = { articles: {size: 'big', list: ['1', '2', '3', '4']}, }; // 想更新state.articles.size这样写,好! const articles = { ...state.articles, size: 'small'}; return { ...state, articles }; // 这样写会导致与list有关的组件而与size无关的组件也被重新渲染,不好!而且深拷贝性能损耗更多! const articles = {size: 'small', list: [...state.articles.list]}; return { ...state, articles };
案例3,错误示范
比如有一个某个操作dispatch
了一个action
,返回新的state
的代码如下:
const user = state.user; user.username = 'foo'; user.name = 'bar'; return { ...state, user };
通过对比,发现showMenu
还是原来的showMenu
,而user
也是原来的user
(强调:state.user
依然是原来的,并没有创建或复制一个新的对象,const user = state.user;
只是复制了引用,不算浅拷贝;不过还是要注意返回的state
跟之前的state
已经不是同一个了),所以,不会重新渲染!
怎么修改?答案如下:
const user = { ...state.user, username: 'foo', name: 'bar', }; return { ...state, user };
案例4,更错误的示范
比如有一个某个操作dispatch
了一个action
,返回新的state
的代码如下:
state.user = {username: '', name: ''}; return state;
这个时候mapStateToProps
函数都懒得理你,它不会执行的。因为state
根本没变。
原文链接:https://blog.csdn.net/kd_2015/article/details/105277509