react-redux Hook API 简介
在跟着redux教程实现Reddit API实例时(参考文章1),想着把类组件用函数组件给改写一下,于是就去看了react-redux的Hook API,最主要就是useSelector、useDispatch和useStore,Hook API让你不必使用connect、mapState和mapDispatch。useSelector需要注意的地方要多一些,文中所有内容均参考react-redux官方教程,就是翻译和总结了一下(参考文章2)。
useSelector
具体形式如下,接收两个参数,第二个参数可选。
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取得。
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 具体实例
需要注意的是,两者代码行数的变化。
类组件形式
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 (
<Picker
value={selectSubreddit}
onChange={(msg) => this.handleChange(msg)}
options={['reactjs', 'frontend']}
lastUpdate &&
<span>Last Updated at: {new Date(lastUpdate).toLocaleTimeString()}</span>
!isFetch &&
<button onClick={() => this.handleRefreshClick(selectSubreddit)}>reFresh</button>
{isFetch && <h2>Loading...</h2>}
posts.length ?
<Posts posts={posts} /> :
<h2>Empty</h2>
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);
函数组件形式
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 (
<Picker
value={subreddit}
onChange={handleChange}
options={['reactjs', 'frontend']}
lastUpdate &&
<span>Last Updated at: {new Date(lastUpdate).toLocaleTimeString()}</span>
!isFetch &&
<button onClick={handleRefreshClick}>reFresh</button>
{isFetch && <h2>Loading...</h2>}
posts.length ?
<Posts posts={posts} /> :
<h2>Empty</h2>