Skip to content

十分钟学会 react-redux #8

@beichensky

Description

@beichensky

前言

本文主要介绍了 react-redux 的用法,以及在各种场景下不同 API 的使用方式和区别。

本文代码是在上一篇博客的基础上进行的,有疑惑的地方可以先查看上一篇博客:轻松掌握 Redux 核心用法

本文已收录在 Github: https://github.com/beichensky/Blog 中,欢迎 Star!

一、准备工作

安装

npm install react-redux
# or
yarn add react-redux

常规用法

  • Provider: 使用 Provider 标签包裹根组件,将 store 作为属性传入,后续的子组件才能获取到 store 中的 statedispatch

  • connect:返回一个高阶组件,用来连接 React 组件与 Redux store,返回一个新的已与 Redux store 连接的组件类

hooks 用法

  • useDispatch:返回一个 dispatch 对象

  • useSelector:接受一个函数,将函数的返回值返回出来

二、Provider 的使用

根目录下 index.js 文件

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import App from "./App";
import store from "./store";
import "./index.css";

ReactDOM.render(
  <React.StrictMode>
    {/* 使用 Provider 标签包裹住根组件,并将 store 作为参数传入 */}
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
);

三、connect 的使用

  • APIconnect([mapStateToProps],[mapDispatchToProps],[mergeProps],[options])

connect 的用法相对复杂一些,接受四个参数,返回的是一个高阶组件。用来连接当前组件和 Redux store

1. mapStateToProps

mapStateToProps:函数类型,接受两个参数: stateownProps(当前组件的 props,不建议使用,会导致重渲染,损耗性能),必须返回一个纯对象,这个对象会与组件的 props 合并

  • (state[, ownProps]) => ({ count: state.count, todoList: state.todos })

2. mapDispatchToProps

mapDispatchToProps:object | 函数

  • 不传递这个参数时,dispatch 会默认挂载到组件的的 props
  • 传递 object 类型时,会把 object 中的属性值使用 dispatch 包装后,与组件的 props 合并
{
    increment: () => ({ type: "INCREMENT" }),
    decrement: () => ({ type: "DECREMENT" }),
}
  • 对象的属性值都必须是 ActionCreator

  • dispatch 不会再挂载到组件的 props

  • 传递函数类型时,接收两个参数:dispatchownProps(当前组件的 props,不建议使用,会导致重渲染,损耗性能),必须返回一个纯对象,这个对象会和组件的 props 合并

(state[, ownProps]) => ({
    dispatch,
    increment: dispatch({ type: "INCREMENT" }),
    decrement: dispatch({ type: "DECREMENT" })
})

3. mergeProps

mergeProps(很少使用) 函数类型。如果指定了这个参数,mapStateToProps()mapDispatchToProps()的执行结果和组件自身的 props 将传入到这个回调函数中。该回调函数返回的对象将作为 props 传递到被包装的组件中。你也许可以用这个回调函数,根据组件的 props 来筛选部分的 state 数据,或者把 props 中的某个特定变量与 ActionCreator 绑定在一起。如果你省略这个参数,默认情况下组件的 props 返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的结果

  • mergeProps(stateProps, dispatchProps, ownProps): props

4. options

  • context?: Object
  • pure?: boolean
  • areStatesEqual?: Function
  • areOwnPropsEqual?: Function
  • areStatePropsEqual?: Function
  • areMergedPropsEqual?: Function
  • forwardRef?: boolean

下面 改写一下 App.jsredux 的用法

  • mapDispatchToProps 参数不传时
import React, { useEffect, useCallback, useState, useMemo } from "react";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import { actionCreators } from "./store";

function App({ count, todos, dispatch }) {
  const [value, setValue] = useState("");

  // 生成包装后的 actionCreator,执行之后就会触发 store 数据的更新
  const { increment, decrement, getAsyncTodos, addTodo } = useMemo(
    () => bindActionCreators(actionCreators, dispatch),
    [dispatch]
  );

  // 初始化 TodoList
  useEffect(() => {
    getAsyncTodos();
  }, [getAsyncTodos]);

  const add = useCallback(() => {
    if (value) {
      // 分发 action
      addTodo(value);
      setValue("");
    }
  }, [value, addTodo]);

  return (
    <div className="App">
      <h1>Hello Redux</h1>
      <p>count: {count}</p>
      <button onClick={increment}>increment</button>
      <button onClick={decrement}>decrement</button>
      <br />
      <br />
      <input
        placeholder="请输入待办事项"
        value={value}
        onChange={e => setValue(e.target.value)}
      />
      <button onClick={add}>add</button>
      <ul>
        {todos.map(todo => (
          <li key={todo}>{todo}</li>
        ))}
      </ul>
    </div>
  );
}

// count、todos 也会被挂载到组件的 props 中
const mapStateToProps = ({ count, todos }) => ({ count, todos });

// 第二个参数没有传递,dispatch 默认会挂载到组件的 props 中
export default connect(mapStateToProps)(App);
  • mapDispatchToProps 参数为对象时
import React, { useEffect, useCallback, useState } from "react";
import { connect } from "react-redux";
import { actionCreators } from "./store";

function App({ count, todos, increment, decrement, getAsyncTodos, addTodo }) {
  const [value, setValue] = useState("");

  // 初始化 TodoList
  useEffect(() => {
    getAsyncTodos();
  }, [getAsyncTodos]);

  const add = useCallback(() => {
    if (value) {
      // 分发 action
      addTodo(value);
      setValue("");
    }
  }, [value, addTodo]);

  return (
    <div className="App">
      <h1>Hello Redux</h1>
      <p>count: {count}</p>
      <button onClick={increment}>increment</button>
      <button onClick={decrement}>decrement</button>
      <br />
      <br />
      <input
        placeholder="请输入待办事项"
        value={value}
        onChange={e => setValue(e.target.value)}
      />
      <button onClick={add}>add</button>
      <ul>
        {todos.map(todo => (
          <li key={todo}>{todo}</li>
        ))}
      </ul>
    </div>
  );
}

// count、todos 会被挂载到组件的 props 中
const mapStateToProps = ({ count, todos }) => ({ count, todos });

// actionCreators 中的 actionCreator 会被 dispatch 进行包装,之后合并到组建的 props 中去
const mapDispatchToProps = { ...actionCreators };

export default connect(mapStateToProps, mapDispatchToProps)(App);
  • mapDispatchToProps 参数为函数时
import React, { useEffect, useCallback, useState } from "react";
import { connect } from "react-redux";
import { actionCreators } from "./store";
import { bindActionCreators } from "redux";

function App({
  count,
  todos,
  increment,
  decrement,
  getAsyncTodos,
  addTodo,
  dispatch
}) {
  const [value, setValue] = useState("");

  // 初始化 TodoList
  useEffect(() => {
    getAsyncTodos();
  }, [getAsyncTodos]);

  const add = useCallback(() => {
    if (value) {
      // 分发 action
      addTodo(value);
      setValue("");
    }
  }, [value, addTodo]);

  return (
    <div className="App">
      <h1>Hello Redux</h1>
      <p>count: {count}</p>
      <button onClick={increment}>increment</button>
      <button onClick={decrement}>decrement</button>
      <br />
      <br />
      <input
        placeholder="请输入待办事项"
        value={value}
        onChange={e => setValue(e.target.value)}
      />
      <button onClick={add}>add</button>
      <ul>
        {todos.map(todo => (
          <li key={todo}>{todo}</li>
        ))}
      </ul>
    </div>
  );
}

// count、todos 会被挂载到组件的 props 中
const mapStateToProps = ({ count, todos }) => ({ count, todos });

// mapDispatchToProps 为函数时,actionCreators 中的 actionCreator 需要自己处理,返回的对象会被合并到组件的 props 中去
const mapDispatchToProps = dispatch => ({
  dispatch,
  ...bindActionCreators(actionCreators, dispatch)
});

export default connect(mapStateToProps, mapDispatchToProps)(App);
  • 如果不需要更新 store 中的数据,则不需要传 mapDispatchToProps 参数
  • 如果不需要自己控制 dispatch,则传递 ActionCreators 对象即可
  • 如果需要自己完全控制,则传递一个回调函数

虽然上面使用 connect 是在 class 组件,但是在函数组件中依然适用。

四、useDispatch 和 useSelector 的使用

上面我们在组件中使用的是 connect,但是在现在这个 hooks 盛行的时代,怎么能只有高阶组件呢,所以下面我们来探究一下 useDispatchuseSelector 的用法。

改写 App.js 文件

import React, { useEffect, useCallback, useState, useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { bindActionCreators } from "redux";
import { actionCreators } from "./store";

function App() {
  const [value, setValue] = useState("");

  // 从 useDispatch 中获取 dispatch
  const dispatch = useDispatch();

  // 生成包装后的 actionCreator,执行之后就会触发 store 数据的更新
  const { increment, decrement, getAsyncTodos, addTodo } = useMemo(
    () => bindActionCreators(actionCreators, dispatch),
    [dispatch]
  );

  // 通过 useSelector 获取需要用到 state 值
  const { count, todos } = useSelector(({ count, todos }) => ({
    count,
    todos
  }));

  // 初始化 TodoList
  useEffect(() => {
    getAsyncTodos();
  }, [getAsyncTodos]);

  const add = useCallback(() => {
    if (value) {
      // 分发 action
      addTodo(value);
      setValue("");
    }
  }, [value, addTodo]);

  return (
    <div className="App">
      <h1>Hello Redux</h1>
      <p>count: {count}</p>
      <button onClick={increment}>increment</button>
      <button onClick={decrement}>decrement</button>
      <br />
      <br />
      <input
        placeholder="请输入待办事项"
        value={value}
        onChange={e => setValue(e.target.value)}
      />
      <button onClick={add}>add</button>
      <ul>
        {todos.map(todo => (
          <li key={todo}>{todo}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

使用 hooks 方式改写之后,感觉简洁了不少,数据来源也很清晰。至于用 connect 还是 hook 的方式,可以根据情况自己选择。

五、总结

  • ReduxAction、Reducer、Store 组成

    • 通过 Reducer 创建 Store
    • 通过 store.dispatch(action) 触发更新函数 reducer
    • 通过 reducer 更新数据
    • 数据更新触发订阅 subscribe
  • 通过 combineReducers 可以合并多个 reducer

  • 通过 applyMiddleware 可以使用插件

  • 通过 bindActionCreators 可以将 ActionCreator 转化成 dispatch 包装后的 ActionCreator

是不是还没有看过瘾呢?没有的话请看我的下一篇博客,详细讲解了 Redux 以及 React-Redux 的实现原理。

六、源码位置

react-redux-demo

如果有写的不对或不严谨的地方,欢迎大家能提出宝贵的意见,十分感谢。

如果喜欢或者有所帮助,欢迎 Star,对作者也是一种鼓励和支持。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions