写点什么

React Redux 组件更新 / 渲染原理 connect 中的 mapStateToProps

作者:HullQin
  • 2022 年 8 月 11 日
    广东
  • 本文字数:2426 字

    阅读完需:约 8 分钟

我是 HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者 HullQin 授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加 Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。


本文深入浅出mapStateToProps,解答:


  • 为什么修改 state,组件未渲染/更新?

  • 如何从新旧 state 判断更新的值、未更新的值,从而决定是否 re-render?


Redux 中,state作为单一的数据源,众所周知,每次更新state都要通过return { ...state, others }来返回一个新的state,但是它是怎么来判断一些组件到底要不要 re-render(刷新、重渲染)呢?尤其是当state层次很深的时候,会有效率问题 or 该刷新时不刷新的问题吗?


其实关键在于这个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我是 HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者 HullQin 授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加 Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。

发布于: 刚刚阅读数: 3
用户头像

HullQin

关注

公众号【线下聚会游戏】 2020.10.07 加入

game.hullqin.cn 我做了一些联机桌游网页:支持2-10人联机的UNO、2-4人联机的斗地主、2人联机的五子棋。无需下载,点开即玩!叫上朋友,即刻开局!不看广告,不做任务,享受「纯粹」的游戏!

评论

发布
暂无评论
React Redux 组件更新/渲染原理 connect 中的 mapStateToProps_CSS_HullQin_InfoQ写作社区