2

React Developer Tools显示数据已更新,组件却没刷新?这不是reducer的坑!

先说结论

view 层未刷新,问题不在 redux,而是 react!react会在每次 state 变更时重渲染。那如何判别是否变更?一句话:基本数据类型直接比较,引用类型比较前后引用是否相等,所以,必须保证,redux 数据树根结点(肯定是引用类型)每次渲染都不一样!也就有如下写法:

注意reducer中return回去的内容,按以下方式书写

    switch (action.type) {
        case 'change_is_login': {
            const thisAction: ChangeIsLoginAction = action;
            const {
                isLogin
            } = thisAction.payload;
            `return {
                ...state,
                isLogin
            };`
        }

Header组件如图
image.png
chrome的redux插件显示数据如图
image.png
显示正常,Header代码如下
import React, { Fragment } from 'react';
import { Link } from 'react-router-dom';
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
import { RootState, HeaderState } from '../../types';
import { actionCreators } from './store';


const Header: React.FC = () => {
    const dispatch = useDispatch();
    const headerState = useSelector<RootState, HeaderState>(state => { return state.header })
    console.log('headerPage receive state is');
    console.log(headerState);

    const {
        isLogin,
    } = headerState

    const handleLogin = () => {
        dispatch(actionCreators.login());
    }
    const handleLogout = () => {
        dispatch(actionCreators.logout());
    }

    return (
        <div>
            <Link to='/'>首页</Link>
            {`islogin: ${isLogin}`}
            <br />
            {
                isLogin ?
                    <Fragment>
                        <Link to='/login'>翻译</Link>
                        <br />
                        <div onClick={handleLogout}>退出</div>
                    </Fragment>
                    : <div onClick={handleLogin}>登陆</div>
            }
        </div >
    )
};

export default Header;

对应login,logout事件的reducer,目的是:改变state中isLogin的值,从而刷新Header

  1. 点击登陆出发点击时间,重设isLogin的action被dispatch至此处
  2. 定义新变量newState指向state
  3. 修改newState.isLogin
  4. 将newState返回
const defaultState: HeaderState = {
    isLogin: true,
}

export default (state = defaultState, action: any) => {
    switch (action.type) {
        case 'change_is_login': {
            const thisAction: ChangeIsLoginAction = action;
            const {
                isLogin
            } = thisAction.payload;
            const newState = state; 
            newState.isLogin = isLogin;
            return newState;
        }
        default:
            return state;
    }
}

逻辑清晰,代码简单看似一气呵成,点击登陆一看!
image.png
Header组件并未刷新
image.png
chrome的redux插件显示,isLogin已经改变。

难道useSelector失效了? 笔者翻找react-redux的仓库,并未找到useSelector失效的issue

经过两个小时的dubug,最终定位到reducer代码,更改如下

const defaultState: HeaderState = {
    isLogin: true,
}

export default (state = defaultState, action: any) => {
    switch (action.type) {
        case 'change_is_login': {
            const thisAction: ChangeIsLoginAction = action;
            const {
                isLogin
            } = thisAction.payload;
            return {
                ...state,
                isLogin
            };
        }

        default:
            return state;
    }
}

之后,操作回归正常,useSelector生效,组件正常刷新
image.png
image.png
点击登陆
image.png
image.png
切换正常。


原因

reducer内数据是不可更改的,官方推荐两种写法更新state

  1. 使用

Object.assign({ }, state, {
   你要添加的内容
})

case  SET_VISIBILITY_FILTER:
    return  Object.assign({}, state,  {
    subState
})
2.当然还有更简洁的写法,ES6的扩展运算符
case  SET_VISIBILITY_FILTER:
    return {
        ...state,
        `subState`
    }

建议读一读redux的文档。

中文文档如下 https://www.redux.org.cn/docs...

英文文档如下 https://redux.js.org/basics/r...


CregskiN
143 声望5 粉丝

焦虑驱动型学习者,喜欢 react 和 node