前端追梦人Redux教程

一.为什么要用Redux?

网页应用中需要管理的状态越来越多, 越来越复杂, redux作为数据的统一管理容器简化了应用的数据管理难度,按照既定的数据存取规则,降低了应用的维护难度。

二.redux的三大原则

  1. 单一数据源
    整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中
  2. state是只读的
    唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
  3. 使用纯函数来执行修改
    为了描述 action 如何改变 state tree ,你需要编写 reducers。
    Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。

三. 基础概念

3.1 Action

Action是一个JavaScript对象

{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

type代表要执行的动作, 当应用规模比较大时,推荐将action单独提取到actionTypes文件中进行维护

3.2 Reducer

是一些纯函数来相应dispatch来的action, 函数内部进行对应动作类型的数据处理,返回新的state

reducer函数的switch的default分支要返回旧的state。

注意每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。

Redux 提供了 combineReducers()函数来生成一个函数,这个函数来调用你的一系列 reducer,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理, 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树

combineReducers 接收一个对象,可以把所有顶级的 reducer 放到一个独立的文件中,通过 export 暴露出每个
reducer 函数,然后使用 import * as reducers 得到一个以它们名字作为 key 的 object

代码示例:

import { combineReducers } from 'redux'
import {
  ADD_TODO,
  TOGGLE_TODO,
  SET_VISIBILITY_FILTER,
  VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilters

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp

3.3 Store

action用来描述发生了什么, reducer函数用来根据action更新state, store将它们联系到一起
store职责如下:

  • 维持应用的 state;
  • 提供 getState() 方法获取 state;
  • 提供 dispatch(action) 方法更新 state;
  • 通过 subscribe(listener) 注册监听器;
  • 通过 subscribe(listener) 返回的函数注销监听器。
    Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store
    在上面代码基础上创建store实例
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp, [state的初始状态,可选参数])

store派发action更新state示例:

import {
  addTodo,
  toggleTodo,
  setVisibilityFilter,
  VisibilityFilters
} from './actions'

// 打印初始状态
console.log(store.getState())

// 每次 state 更新时,打印日志
// 注意 subscribe() 返回一个函数用来注销监听器
const unsubscribe = store.subscribe(() => console.log(store.getState()))

// 发起一系列 action
store.dispatch(addTodo('Learn about actions'))
store.dispatch(addTodo('Learn about reducers'))
store.dispatch(addTodo('Learn about store'))
store.dispatch(toggleTodo(0))
store.dispatch(toggleTodo(1))
store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED))

// 停止监听 state 更新
unsubscribe()

四. 与React整合

安装react-redux库, 该库用来将redux连接到react
一个简单实例

// store.js
import { createStore } from "redux";

const initialState = {
  counter: 1,
};
function reducers(state, action) {
  switch (action.type) {
    case "increment":
      return {
        ...state,
        counter: state.counter + 1,
      };
    case "decrement":
      return {
        ...state,
        counter: state.counter - 1,
      };
    default:
      return state;
  }
}

export default createStore(reducers, initialState);

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



render(
  <Provider store={store}>
    <React.StrictMode>
      <App />
    </React.StrictMode>
  </Provider>,
  document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals(console.log);

// test.js
import React from "react";
import { connect } from "react-redux";

const mapStateToProps = (state) => ({
  counter: state.counter,
});

const mapDispatchToProps = (dispatch) => ({
  increment: () =>
    dispatch({
      type: "increment",
    }),
  decrement: () =>
    dispatch({
      type: "decrement",
    }),
});

function Home({ counter, increment, decrement }) {
  return (
    <>
      <span>现在的counter值为: {counter}</span>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </>
  );
}

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

五. 异步Action

安装redux-thunk, 也可以使用redux-saga构建构建更加复杂的异步Action

// actionCreators.js
import { INCREMENT_COUNTER, DECREMENT_COUNTER, LOGGER_COUNTER } from './actionTypes';

export const increment = () => ({
    type: INCREMENT_COUNTER
});

export const decrement = () => ({
    type: DECREMENT_COUNTER
});

export const logCounter = () => ({
    type: LOGGER_COUNTER
});


/**
 * 创建了一个异步函数
 */
export const delayLogCurrentCounterValue = () => {
    return dispatch => {
        setTimeout(() => {
            // 这里可以继续派发同步或者异步action
            dispatch(logCounter());
        }, 2000);
    }
};
// actionTypes.js
export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';
export const LOGGER_COUNTER = 'LOGGER_COUNTER';
// store.js
import { createStore, applyMiddleware } from "redux";
import { createLogger } from "redux-logger";
import thunkMiddleware from "redux-thunk";
import {
  INCREMENT_COUNTER,
  DECREMENT_COUNTER,
  LOGGER_COUNTER,
} from "./actionTypes";

const initialState = {
  counter: 1,
};

function reducers(state = initialState, action) {
  switch (action.type) {
    case INCREMENT_COUNTER:
      return {
        ...state,
        counter: state.counter + 1,
      };
    case DECREMENT_COUNTER:
      return {
        ...state,
        counter: state.counter - 1,
      };
    case LOGGER_COUNTER:
      console.log("============延迟打印start===================");
      console.log(`当前counter的值为: ${state.counter}`);
      console.log("============延迟打印end=============");
      return state;
    default:
      return state;
  }
}

const loggerMiddleware = createLogger();

export default createStore(
  reducers,
  initialState,
  applyMiddleware(
    thunkMiddleware, // 允许我们dispatch函数
    loggerMiddleware // 打印action日志
  )
);

// Test.js
import React from "react";
import { connect } from "react-redux";
import * as actionCreators from "./store/actionCreators";

const mapStateToProps = (state) => ({
  counter: state.counter,
});

function Home({ counter, increment, decrement, delayLogCurrentCounterValue }) {
  return (
    <>
      <span>现在的counter值为: {counter}</span>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={delayLogCurrentCounterValue}>触发redux-thunk延迟打印</button>
    </>
  );
}

export default connect(mapStateToProps, actionCreators)(Home);

我们可以使用redux-actions库来简化action的创建

// actionCreators.js
import { INCREMENT_COUNTER, DECREMENT_COUNTER, LOGGER_COUNTER } from './actionTypes';
import { createAction } from 'redux-actions';
export const increment = createAction(INCREMENT_COUNTER);
export const decrement = createAction(DECREMENT_COUNTER);
export const logCounter = createAction(LOGGER_COUNTER);

/**
 * 创建了一个异步函数
 */
export const delayLogCurrentCounterValue = () => {
    return dispatch => {
        setTimeout(() => {
            // 这里可以继续派发同步或者异步action
            dispatch(logCounter());
        }, 2000);
    }
};

//store.js
import { createStore, applyMiddleware } from "redux";
import { createLogger } from "redux-logger";
import thunkMiddleware from "redux-thunk";
import { handleActions } from "redux-actions";
import {
  INCREMENT_COUNTER,
  DECREMENT_COUNTER,
  LOGGER_COUNTER,
} from "./actionTypes";

const initialState = {
  counter: 1,
};

const reducers = handleActions(
  {
    [INCREMENT_COUNTER]: (state, action) => ({
      counter: state.counter + 1,
    }),

    [DECREMENT_COUNTER]: (state) => ({
      counter: state.counter - 1,
    }),
    [LOGGER_COUNTER]: (state) => {
      console.log("============延迟打印start===================");
      console.log(`当前counter的值为: ${state.counter}`);
      console.log("============延迟打印end=============");
      return state;
    },
  },
  initialState
);

const loggerMiddleware = createLogger();

export default createStore(
  reducers,
  initialState,
  applyMiddleware(
    thunkMiddleware, // 允许我们dispatch函数
    loggerMiddleware // 打印action日志
  )
);

使用redux的chrome插件进行调试

import { createStore, applyMiddleware, compose } from "redux";
import { createLogger } from "redux-logger";
import thunkMiddleware from "redux-thunk";
import { handleActions } from "redux-actions";
import {
  INCREMENT_COUNTER,
  DECREMENT_COUNTER,
  LOGGER_COUNTER,
} from "./actionTypes";

const initialState = {
  counter: 1,
};

const reducers = handleActions(
  {
    [INCREMENT_COUNTER]: (state) => ({
      counter: state.counter + 1,
    }),

    [DECREMENT_COUNTER]: (state) => ({
      counter: state.counter - 1,
    }),
    [LOGGER_COUNTER]: (state) => {
      console.log("============延迟打印start===================");
      console.log(`当前counter的值为: ${state.counter}`);
      console.log("============延迟打印end=============");
    },
  },
  initialState
);

const loggerMiddleware = createLogger();

// 判断chrome是否安装了redux调试插件
const reduxDevtools = window.devToolsExtension
  ? window.devToolsExtension({ trace: true })
  : () => {};

export default createStore(
  reducers,
  compose(
    applyMiddleware(
      thunkMiddleware, // 允许我们dispatch函数
      loggerMiddleware // 打印action日志
    ),
    reduxDevtools
  )
);

六. 中间件的使用和编写

redux中间件技术提供的是位于 action 被发起之后,到达 reducer 之前的扩展点, 我们可以基于此进行日志记录、创建崩溃报告、调用异步接口或者路由等等。
简单示例

// 日志打印
const logger = store => next => action => {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}

// 崩溃报告
const crashReporter = store => next => action => {
  try {
    return next(action)
  } catch (err) {
    console.error('Caught an exception!', err)
    Raven.captureException(err, {
      extra: {
        action,
        state: store.getState()
      }
    })
    throw err
  }
}

然后是将它们引用到 Redux store 中:

import { createStore, combineReducers, applyMiddleware } from 'redux'

const todoApp = combineReducers(reducers)
const store = createStore(
  todoApp,
  // applyMiddleware() 告诉 createStore() 如何处理中间件
  applyMiddleware(logger, crashReporter)
)

现在任何被发送到 store 的 action 都会经过 logger 和 crashReporter:

七. 使用reselect库计算衍生数据

  • 该库用来对mapStateToProps中的state取值进行缓存, 如果store中该state值没有变化,则直接返回缓存值,不重新进行计算,提高了性能。

  • 我们通过使用createSelector函数来创建带缓存的mapStateToProps

  • createSelector函数的2个参数分别为input-selectors数组和转换函数

  • 如果 state tree 的改变会引起 input-selector 值变化,那么 selector 会调用转换函数,传入 input-selectors 作为参数,并返回结果。如果 input-selectors 的值和前一次的一样,它将会直接返回前一次计算的数据,而不会再调用一次转换函数。
    简单使用示例:

// selectors.js
import { createSelector } from 'reselect'

const getVisibilityFilter = state => state.visibilityFilter
const getTodos = state => state.todos

export const getVisibleTodos = createSelector(
  [getVisibilityFilter, getTodos],
  (visibilityFilter, todos) => {
    switch (visibilityFilter) {
      case 'SHOW_ALL':
        return todos
      case 'SHOW_COMPLETED':
        return todos.filter(t => t.completed)
      case 'SHOW_ACTIVE':
        return todos.filter(t => !t.completed)
    }
  }
)

// VisibleTodoList.js
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { getVisibleTodos } from '../selectors'

const mapStateToProps = state => {
  return {
    todos: getVisibleTodos(state)
  }
}

const mapDispatchToProps = dispatch => {
  return {
    onTodoClick: id => {
      dispatch(toggleTodo(id))
    }
  }
}

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

export default VisibleTodoList

八.一些redux的学习资源

  1. redux学习资源
  2. 使用Redux Toolkit简化redux的配置及使用
  3. redux官网
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值