react-redux学习整理

react-redux学习整理
 最后更新于 2024年10月03日 02:55:57

概念

React-Redux 将所有组件分成两大类:UI 组件(presentational component)容器组件(container component)

UI 组件

  • 只负责 UI 的呈现,不带有任何业务逻辑
  • 无状态(即内部没有 state
  • 所有数据都来自 props
  • 不使用任何 Redux 的 API

容器组件

  • 负责管理数据和业务逻辑,不负责 UI 的呈现
  • 带有内部状态
  • 使用 Redux 的 API

方法:

connect

用于从 UI 组件生成容器组件

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

class TodoList extends React.Component {
  constructor (props) {
    super(props);

    // props.todos
  }
  // ...TODO
}

const mapStateToProps = (stateownProps) => {
  return {
    todos: state.stateTodos
  }
}

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

connect 方法接受两个参数: mapStateToPropsmapDispatchToProps

  • mapStateToProps: 将 redux 中的 state 映射到 UI 组件的 props,UI组件内使用 props 获取值。【可省略】

    该方法会订阅 Store,每当 state 更新的时候,就会自动执行,从而触发 UI 组件的相关事件。 如果省略,则不会订阅 state,即 Store 更新不会引起 UI 组件的更新


    mapStateToProps 该方法包含两个参数:
    • state
    • ownProps:容器组件的props对象
  • mapDispatchToProps:将用户对 UI 组件的操作映射成 Action。UI组件通过该函数触发 action 继而改变 store
    可以是一个函数,也可以是一个对象。
    • 为函数时 会得到 dispatchownProps(容器组件的 props 对象)两个参数。 需返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action
      const mapDispatchToProps = (dispatch,  ownProps) => {
        return {
          onClick: () => {
            dispatch({
              type: 'SET_VISIBILITY_FILTER',
              filter: ownProps.filter
            });
          }
        };
      }
      
    • 为对象时 它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出
      const mapDispatchToProps = {
        onClick: (filter) => {
          type: 'SET_VISIBILITY_FILTER',
          filter: filter
        };
      }
      

Provider 组件

import { Provider } from 'react-redux'
import { createStore } from 'redux'
import reducers from './reducers'
import App from './components/App'

let store = createStore(reducers);

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

Provider 组件当作根组件包裹其它组件,这时 App 下的所有子组件就都可以拿到 state

QA

1、问题: 通过 reducer 更新 state,虽然在 reducer 中改变了 state 的值,但是页面没有重新渲染

const initStore = [0, 1];

export default function (state = initStore, action) {
  switch (action.type) {
    case 'DELETE':
      state.splice(action.index, 1);
      console.log('state:',state);// 输出state的值已经改变
      return state;
  }
}

原因:reducer 只是一个纯函数,接收旧的 state 和 action,并返回新的 state 永远不要在 reducer 里做这些操作:

修改传入参数; 执行有副作用的操作,如 API 请求和路由跳转; 调用非纯函数,如 Date.now() 或 Math.random()。

Store 会把两个参数传入 reducer: 当前的 state 和 action,所以不能直接修改state,redux会比较新旧state的值,直接修改state会导致store内部的也发生改变,那么新旧state也就没有发生变化。页面就不会重新渲染。

解决: 不要修改 state。 使用 Object.assign()ES7 的特性 { ...state, ...newState }

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      // Object.assign() 方法
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      });

      // es7 语法特性
      state.splice(action.index,1);
      return [...state];
    default:
      return state
  }
}

2、问题: Actions must be plain objects. Use custom middleware for async actions

原因: 使用异步 actions 时,需要 redux-thunk 中间件支持

解决:

// 增加 redux-thubk 中间件

import { createStore as _createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import promiseMiddleware from 'redux-promise';

// 合并后的reducer
import reducer from './modules/reducer';

export default function createStore (initialState = {}) {
  const store = createStore(
    reducer,
    initialState,
    // 组合多个 middleware 形成 middleware链,扩展 actions
    applyMiddleware(thunkMiddleware, promiseMiddleware)
  );

  return store;
}

示例

项目内多个 store

/**
 * 程序主入口 index.js
 */
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import RouterMap from './router/routerMap';
import configureStore from './redux/index';

const store = configureStore();

ReactDOM.render(
  <Provider store={store}>
    <RouterMap />
  </Provider>,
  document.getElementById('app')
);


/**
 * index.jsx
 */
import { combineReducers, createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import promiseMiddleware from 'redux-promise';

// 各reducer
import user from '../reducers/user';

// 组合多个reducer为一个
const rootReducer = combineReducers({
  user
});

export default function createStore (initialState = {}) {
  const store = createStore(
    rootReducer,
    initialState,
    // 组合多个 middleware 形成 middleware链,扩展 actions
    applyMiddleware(thunkMiddleware, promiseMiddleware)
  );

  return store;
}


/**
 * user.jsx
 * @desc store、actions、reducer
 */
// ActionType
const SAVE_USER_TOKEN= 'SAVE_USER_TOKEN';
const CLEAR_USER_TOKEN = 'CLEAR_USER_TOKEN';
const SAVE_USER_INFO = 'SAVE_USER_INFO';

// Store
const initialState = {
  token: '',
  userInfo: {}
};

// Reducer
export default function user (state = initialState, action) {
  switch (action.type) {
    case SAVE_USER_TOKEN:
      return { ...state, token: action.data }

      break;
    case CLEAR_USER_TOKEN:
      return { ...state, token: '' }

      break;
    case SAVE_USER_INFO:
      return { ...state, userInfo: action.data }

      break;
    default:
      return state;
  }
}

// Actions
// 存储token
export function ActionSaveToken (token) {
  return { type: SAVE_USER_TOKEN, data: token }
}

// 清除token
export function ActionClearToken () {
  return { type: CLEAR_USER_TOKEN }
}

// 获取用户信息
export function ActionGetUserInfo () {
  return dispatch => {
    fetch.get('/user/getUserDetail')
      .then((res) => {
        // 触发对应的 reducer
        dispatch({ type: SAVE_USER_INFO, data: res.data });
      }, () => {
        dispatch({ type: SAVE_USER_INFO, data: {} });
      });
  }
}


/**
 * SiginIn.jsx
 */
import { connect } from 'react-redux';
import { ActionSaveToken, ActionGetUserInfo } from '../../redux/reducers/user';

// 第一种写法 (装饰器,需要额外配置)
@connect(
  state => {
    return {
      token: state.user.token
    };
  }, {
    ActionSaveToken,
    ActionGetUserInfo
  }
)

class User extends React.Component {
  // ...TODO
  // functions
  this.props.saveToken('token content');
}

// 第二种写法
const mapStateToProps = (state) => {
  return {
    token: state.user.token
  }
}

const mapDispatchToProps = () => {
  return {
    saveToken: (token) => dispatch(ActionSaveToken(token)),
    getUserInfo: () => dispatch(ActionGetUserInfo())
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(User);

// 第三种写法
export default connect(
  (state) => ({
    token: state.user.token
  }),
  (dispatch) => ({
    saveToken: (token) => dispatch(ActionSaveToken(token)),
    getUserInfo: () => dispatch(ActionGetUserInfo())
  })
)(LoginIn);

使用 装饰器 decorator @connect

  1. 首先执行
 yarn add babel-plugin-transform-decorators-legacy -S
  1. 配置相关的文件
// 在 `.babelrc` 文件新增
"plugins": [
  "transform-decorators-legacy"
]

// webpack.config.js 中的 loader 处理器增加配置
{
  test: /\.jsx$/,
  exclude: /node_modules/,
  use: {
    loader: 'babel-loader',
    options: {
      presets: ['env', 'react', 'es2015'],
      plugins: ['transform-decorators-legacy']
    }
  }
}
  1. 代码中开始使用
// 基于第二个示例代码做如下更新
import { connect } from 'react-redux';
import { UpdateCOS } from '../redux/modules/global';

@connect(
  state => {
    return {
      nowDate: state.global.nowDate,
      cos: state.global.COS
    };
  }, {
    UpdateCOS
  }
)

export default class Records extends React.Component {
  // ...TODO
}

备注 vscode 编辑器 中出现如下警告

对修饰器的实验支持是一项将在将来版本中更改的功能。设置+"experimentalDecorators"+选项以删除此警告。

文件\首选项\设置 中搜索 experimentalDecorators,在用户设置中 添加/修改为

"compilerOptions": {
  "experimentalDecorators": true
}
// or
"javascript.implicitProjectConfig.experimentalDecorators": true