brunoyang / blog

134 stars 13 forks source link

react-redux 的 hooks #24

Open brunoyang opened 5 years ago

brunoyang commented 5 years ago

在v7.1中,react-redux加上了hooks,接下来我们就来看看新hooks,以及需要如何修改组件。 如果你还不清楚什么是hooks,建议阅读此文档

hooks

useSelector

签名:

const result : any = useSelector(selector : Function, equalityFn? : Function)

举例来说:

import { shallowEqual, useSelector } from 'react-redux'
const count = useSelector(stoer => store.count, shallowEqual); // 第二个参数可选,也可以换成自定义的比较函数

useDispatch

用法很简单

const dispatch = useDispatch;
dispatch({ type: 'action1' })

使用connect的写法

import React from 'react';
import { connect } from 'react-redux';

function componentWithConnect({ count, addCount }) {
  return (
    <>
      <span>{count}</span>
      <button onClick={addCount}>点击</button>
    </>
  );
}

const mapStateToProps = (store) => ({
  count: store.count,
});
const mapDispatch = (dispatch) => ({
  addCount: () => dispatch({ type: 'addCount' }),
});
export default connect(mapStateToProps, mapDispatch)(componentWithConnect);

换成hooks的写法

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';

export default function componentWithHooks() {
  const { count } = useSelector(store => ({
    count: store.count,
  }));
  const dispatch = useDispatch();
  const addCount = () => dispatch({ type: 'addCount' });

  return (
    <>
      <span>{count}</span>
      <button onClick={addCount}>点击</button>
    </>
  );
}

在新组件中,我们去掉了connect,使用了useSelectoruseDispatch两个hooks。这样就不用再从props传入,让逻辑更为内聚。(另外还有一个好处就是在React Devtools里也少了一层嵌套)

消失的useActions?

可能有的同学会有疑问,useDispatch并没有完全代替mapDispatchToProps,为什么不加上useActions呢? 实际上在beta版本中,react-redux是带有useActions的,但在dan的建议下,去掉了这个api。为什么呢?其实只要自己模拟一下,你就能知道为什么了。

function someComponet() {
  // !!!!没有 useActions 这个api!!!!
  const actions = useActions((dispatch) => ({
    action1: () => dispatch({ type: 'action1' }),
    action2: () => dispatch({ type: 'action2' }),
  }));

  useEffect(() => {
    actions.action1();
    actions.action2();
  }, [actions.action1, actions.action2]);
}

看看为了发送一个dispatch,写了多少模板代码,要是有更多的action,useEffect的依赖列表就会变得更长。此外,在得到actions后,几乎是立即就又被解包了,在使用connect的情况下可能还是可以接受的,但在使用了hooks后,无疑是画蛇添足。 其实在过去,通过mapDispatchToProps() => dispatch({ type: 'action1' })包装成一个actionCreator,是不是有过分迷恋这种『一行缩写』的嫌疑呢?更不要说这种缩写会让阅读代码的人搞不清数据流。 我们再来看如果直接使用useDispatch是怎么样的

function someComponet() {
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch({ type: 'action1' });
    dispatch({ type: 'action2' });
  }, [dispatch]);
}

是不是更简单,也更清晰了呢。

自定义hooks

在很多情况下,useDispatch已经够用,但在一些复杂情况下,还是需要自定义hooks的。如要在多个组件间共享逻辑,自定义hooks是个不错的选择。

// complexActions.js
const complexActions = () => {
  const dispatch = useDispatch();
  dispatch({ type: 'actions1' });
  dispatch({ type: 'actions2' });
  someRequest().then(() => {
    dispatch({ type: 'actions3' }); 
  });
  // balbala...
};

// componetA.js
function componentA() {
  useEffect(() => {
    complexActions();
  }, [complexActions]);

  return (...);
}

// componetB.js
function componentB() {
  useEffect(() => {
    complexActions();
  }, [complexActions]);

  return (...);
}

性能优化建议

触发dispatch后,useSelector被触发(因为useSelector是使用useEffect实现,useEffect的依赖就有store),当发现前后两次state不一样时,会触发重渲染。此时的问题是当不相关的值被修改,本组件仍然会重渲染。可以使用reselect来减少这种情况,具体怎么做,可以参考官方文档。 另外,connect也会对props做一次浅比较,防止重渲染。所以去掉connect之后,就需要使用React.memo去做了。

import React from 'react';
function componentA(props) { return <>...</> }
export default React.memo(componentA);

参考

crazyair commented 5 years ago

—_—