redux在类组件和函数式组件中的使用及优化

3,828 阅读3分钟

redux 是什么

redux 是一种独立的库,并不是专属于 react 生态的。

redux 主张单向数据流,不能直接更改 store,即需要通过 disptch(action) -> reducer -> store 的方式更改 store

redux 中重要的几个方法:

  1. createStore 创建store
  2. store.dispatch 触发action
  3. store.subscribe 订阅store变化

有了2,3方法,我们就可以和视图进行交互了,参考 React 和 Redux 的交互流程图(图片不是自己画的)

react-redux 是什么

那么 react 和 redux 之间要联系起来,就需要使用 react-redux,看名字就能看出来它的作用。

我们都知道当 react 组件的 props 或 state 有变化时,组件就会更新。所以将 react 和 redux 联系到一起的关键就是监听 store 的变化,并将store的变化通过 props 传给组件

在没有 react hooks 时,我们想在组件中使用store,需要使用connect方法,具体代码如下:


class Index extends React.Component{
    // ...
}
const mapStateToProps = (store, ownProps) => {
    return {
        user: store.user
    }
}
const mapDispatchToProps = (dispatch) => {
    return {
        setUserInfo(userInfo){
            dispatch({
                type: 'SET_USER_INFO',
                payload: userInfo
            })
        }
    }
}
export default connect(mapStateToProps, mapDispatchToState)(Index)

connect 方法是做什么的

可以看到,connect 方法接收两个函数参数,并返回一个函数,返回的函数的参数是我们定义的组件。

简单点说,connect 方法返回一个高阶组件,并且将mapStateToProps 和 mapDispatchToProps 的执行结果作为被包裹组件的 props 属性。

connect 为什么可以在 store 改变时更新视图?

因为 connect 中订阅了 store 的更新,当 store 更新时,就会重新执行 mapStateToProps,将结果重新注入。从而使视图更新。

connect 方法带来的问题

在早期的 react-redux 版本中,connect 会在返回的高阶组件中 shouldComponentUpdate 中做一次浅比较,在最近的版本中,改用 hooks 实现了同样的逻辑。来防止被包裹的组件做无用的 render。

这虽然能保证被包裹的组件不做无用的 render。但是mapStateToProps 每次 store 更新都要重新执行,不管此次更新是否与自己有关。当 mapStateToProps 中有耗时操作时,会影响性能

reselect

假设有两个 store, counterStore 和 userStore。

没有使用 reselect,当 counterStore 更新,都会打印 浪费时间的计算,即便 userStore 没有更新。

// 浪费时间的计算
function getUserName(user) {
  console.log("浪费时间的计算");
  for (var i = 0; i < 10000; i++) {}
  return user.name;
}
const mapStateToProps = (state, ownProps) => {
  console.log("class user state 计算");
  const { user } = state;
  return {
    name: getUserName(user),
    age: user.age
  };
};

使用reselect后,当更新 counterStore,不会再打印浪费时间的计算

// 浪费时间的计算
function getUserName(user) {
  console.log("浪费时间的计算");
  for (var i = 0; i < 10000; i++) {}
  return user.name;
}

const getReselectUserName = createSelector(
  [state => state.user],
  getUserName
);
const mapStateToProps = (state, ownProps) => {
  console.log("class user state 计算");
  const { user } = state;
  return {
    name: getReselectUserName(state),
    age: user.age
  };
};

react-redux hooks

hooks 写法

首先,与 class 组件一样,都需要 store context。

<Provider store={store}>
    <App/>
</Provider>

其次,订阅传递参数时,替换 connect 写法,引入了 useSelector,用法如下

const { age, name } = useSelector(state => {
    console.log("use selector 计算");

    return {
      name: state.user.name,
      age: state.user.age
    };
  });

更新时,使用 useDispatch,用法如下

const addAge = useCallback(() => {
    dispatch(
      setUserInfoAction({
        age: age + 1
      })
    );
  }, [age, dispatch]);

使用 hooks 需要小心

上面的 hooks 就是一个错误示例,如果没看出来,就说明本文对你还是有收获的。

因为 useSelector 返回的结果和上次不同,就会触发强制渲染。而默认比较方式是 ===。官方给出三种解决方案。

  1. 多次调用useSelectore,每次只返回单一属性
  2. 使用shallowEqual
  3. 使用reselect
// 1
const name = useSelector(state=>state.user.name)
const age = useSelector(state=>state.user.age)
// 2
const {name, age} = useSelector(state=>{
    return {
        name: state.user.name,
        age: state.user.age
    }
}, shallowEqual)
// 3
// 组件外
const userInfoSelector = createSelector(
  state => state.user,
  user => {
    console.log("use selector 计算");
    return { name: user.name, age: user.age };
  }
);
// 组件内
const { age, name } = useSelector(userInfoSelector);

结尾

完整的demo地址:codesandbox.io/s/ceshirese…