react-redux Hook API 简介

2022-10-28 13:36:24 浏览数 (2)

在跟着redux教程实现Reddit API实例时(参考文章1),想着把类组件用函数组件给改写一下,于是就去看了react-redux的Hook API,最主要就是useSelector、useDispatch和useStore,Hook API让你不必使用connect、mapState和mapDispatch。useSelector需要注意的地方要多一些,文中所有内容均参考react-redux官方教程,就是翻译和总结了一下(参考文章2)。

useSelector


具体形式如下,接收两个参数,第二个参数可选。

代码语言:javascript复制
useSelector(selector: Function, equalityFn?: Function)

store中的state是selector的唯一参数,可以从redux store中获取数据。

selector应该是一个纯函数,因为它潜在性地会在任意时刻执行多次。

useSelector()还订阅了store,所以除了在函数组件被渲染时会被调用,当每次dispatch action时也会被调用。

selector可以返回任何值,不一定如mapState一样是个对象。而且这个返回值即是useSelector()的返回值。

当dispatch action后useSelector()会将之前的返回值和现在的返回值进行浅比较,注意使用的是reference equality ===来比较的,而connect是使用shallow equality ==来比较的,如果相等的话就不更新UI,如果不相等就强制更新UI。

如果在一个函数组件中调用了多次useSelector(),就会生成多个独立的对store的订阅,但是因为react的批量更新机制,当每次dispatch action时,还是只返回一个新值。

注意不要用useSelector()中的selector以整个对象的形式返回store state,因为每次返回的都是一个新对象,依据第五条的比较方式来说,肯定会重新触发更新的,造成不必要的性能浪费。所以要使用多个useSelector()去分别获取store中的state,或者使用第二个参数。

selector无法访问自身的props(这里我认为是selector内部无法获取),但是可以通过闭包或者a curried selector取得。

代码语言:javascript复制
export const TodoListItem = props => {
  // 以下就是闭包形式的获取
  const todo = useSelector(state => state.todos[props.id])
  return <div>{todo.text}</div>

当使用memoizing selector时需格外小心。这一点还没有理解清楚,对我来说还是有点儿难的。

useDispatch


和dispatch一样,用于触发action。需要注意的是,当将触发函数通过props传入到子组件中,在子组件中触发时,要使用callback Hook以避免不必要的渲染。

useStore


获取整个store,但是并不会订阅store的变化,所以当dispatch action时,不会自动更新。

useAction


不常用

useShallowEqualSelector


不常用

Reddit API 具体实例


需要注意的是,两者代码行数的变化。

类组件形式

代码语言:javascript复制
class AsyncApp extends Component {
  componentWillReceiveProps(nextProps) {
    const { selectSubreddit } = nextProps
    const { dispatch } = this.props
    // 针对handleChange函数,当selectSubreddit一样时,不需要dispatch action    
    if (selectSubreddit !== this.props.selectSubreddit) {
      dispatch(fetchPostsIfNeeded(selectSubreddit))
    }
  }
  componentDidMount() {
    const { dispatch, selectSubreddit } = this.props
    dispatch(fetchPostsIfNeeded(selectSubreddit))
  }
  handleChange(subreddit) {
    const { dispatch } = this.props
    dispatch(selectSubreddit(subreddit))
  }
  handleRefreshClick(subreddit) {
    const { dispatch } = this.props
    dispatch(invalidateSubreddit(subreddit))
    // 刷新时,必须要dispatch action
    dispatch(fetchPostsIfNeeded(subreddit))
  }
  render() {
    const { selectSubreddit, isFetch, lastUpdate, posts } = this.props
    return (
      <div>
        <Picker
          value={selectSubreddit}
          onChange={(msg) => this.handleChange(msg)}
          options={['reactjs', 'frontend']}
        />
        <p>
          {
            lastUpdate &&
            <span>Last Updated at: {new Date(lastUpdate).toLocaleTimeString()}</span>
          }
        </p>
        {
          !isFetch &&
          <button onClick={() => this.handleRefreshClick(selectSubreddit)}>reFresh</button>
        }
        {isFetch && <h2>Loading...</h2>}
        {
          posts.length ?
            <Posts posts={posts} /> :
            <h2>Empty</h2>
        }
      </div>
    )
  }
}
const mapStateToProps = (state) => {
  const { selectSubreddit, postsBySubreddit } = state
  const { isFetch, didInvalidate, lastUpdate, items } = postsBySubreddit[selectSubreddit] || {
    isFetch: true,
    items: []
  }
  return {
    selectSubreddit,
    isFetch,
    didInvalidate,
    lastUpdate,
    posts: items
  }
}
export default connect(mapStateToProps)(AsyncApp);

函数组件形式

代码语言:javascript复制
const AsyncApp = memo(() => {
  const subreddit = useSelector((state) => state.selectSubreddit)
  const { 
    isFetch, 
    lastUpdate, 
    didInvalidate,
    items:posts 
  } = useSelector((state) => state.postsBySubreddit[subreddit]) || {
    isFetch: true,
    items: []
  }
  const dispatch = useDispatch()
  useEffect(() => {
    dispatch(fetchPostsIfNeeded(subreddit))
  // 第二个参数同样是针对handleChange,当subreddit一样,则不dispatch action    
  },[subreddit])
  function handleChange(subreddit) {
    dispatch(selectSubreddit(subreddit))
  }
  function handleRefreshClick() {
    dispatch(invalidateSubreddit(subreddit))
    // 刷新时必须要dispatch action    
    dispatch(fetchPostsIfNeeded(subreddit))
  }
  return (
    <div>
        <Picker
          value={subreddit}
          onChange={handleChange}
          options={['reactjs', 'frontend']}
        />
        <p>
          {
            lastUpdate &&
            <span>Last Updated at: {new Date(lastUpdate).toLocaleTimeString()}</span>
          }
        </p>
        {
          !isFetch &&
          <button onClick={handleRefreshClick}>reFresh</button>
        }
        {isFetch && <h2>Loading...</h2>}
        {
          posts.length ?
            <Posts posts={posts} /> :
            <h2>Empty</h2>
        }
      </div>
  )
})
export default AsyncApp

参考文章

  1. http://cn.redux.js.org/docs/advanced/ExampleRedditAPI.html
  2. https://react-redux.js.org/api/hooks

0 人点赞