redux 简单理解

131 阅读4分钟

redux文档

英文文档:redux.js.org/

中文文档:www.redux.org.cn/

Github:github.com/reduxjs/red…

原理图: redux.png

常用方法

import { combineReducers } from 'redux'

三大原则

  • 单一数据源
  • State 是只读的
  • 使用纯函数来执行修改

三大模块

  • Action
    • Action 是把数据从应用(译者注:这里之所以不叫 view 是因为这些数据有可能是服务器响应,用户输入或其它非 view 的数据 )传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store。

/*
 * action 类型
 */

export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'

/*
 * 其它的常量
 */

export const VisibilityFilters = {
  SHOW_ALL: 'SHOW_ALL',
  SHOW_COMPLETED: 'SHOW_COMPLETED',
  SHOW_ACTIVE: 'SHOW_ACTIVE'
}

/*
 * action 创建函数
 */

export function addTodo(text) {
  return { type: ADD_TODO, text }
}

export function toggleTodo(index) {
  return { type: TOGGLE_TODO, index }
}

export function setVisibilityFilter(filter) {
  return { type: SET_VISIBILITY_FILTER, filter }
}

  • Reducer
    • Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。

处理多个 action

还有两个 action 需要处理。就像我们处理 SET_VISIBILITY_FILTER 一样,我们引入 ADD_TODO 和 TOGGLE_TODO 两个actions 并且扩展我们的 reducer 去处理 ADD_TODO.

import {
  ADD_TODO,
  TOGGLE_TODO,
  SET_VISIBILITY_FILTER,
  VisibilityFilters
} from './actions'

...

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    default:
      return state
  }
}

拆分 Reducer

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
  }
}

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

function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}

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

最后,Redux 提供了 combineReducers() 工具类来做上面 todoApp 做的事情,这样就能消灭一些样板代码了。有了它,可以这样重构 todoApp

import { combineReducers } from 'redux'

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp

注意上面的写法和下面完全等价:

export default function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}

源码

reducers.js

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
  • Store

Store 就是把# Action和# Reducer 它们联系到一起的对象。Store 有以下职责:

再次强调一下 Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store。

import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)

createStore() 的第二个参数是可选的, 用于设置 state 初始状态。这对开发同构应用时非常有用,服务器端 redux 应用的 state 结构可以与客户端保持一致, 那么客户端可以将从网络接收到的服务端 state 直接用于本地数据初始化。

let store = createStore(todoApp, window.STATE_FROM_SERVER)

发起 Actions

现在我们已经创建好了 store ,让我们来验证一下!虽然还没有界面,我们已经可以测试数据处理逻辑了。

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();

可以看到 store 里的 state 是如何变化的:

可以看到,在还没有开发界面的时候,我们就可以定义程序的行为。而且这时候已经可以写 reducer 和 action 创建函数的测试。不需要模拟任何东西,因为它们都是纯函数。只需调用一下,对返回值做断言,写测试就是这么简单。

源码

index.js

import { createStore } from 'redux'
import todoApp from './reducers'

let store = createStore(todoApp)

代码:

  1. 定义 action , reducer, store
import {createStore} from 'redux';


// 定义常量
export const ADD = "ADD";
export const MINUS = "MINUS";

function reducer(state={count: 0}, action) {
    console.log('reducer -- action', action , state);

    switch (action.type){
        case ADD:
            return  {count: state.count + 1};
            break;
        case MINUS:
            return {count: state.count - 1};
            break;
        default:
            return state;

    }
}

const store = createStore(reducer);
export default store;



  1. 组件使用
import React from "react";
// 导入store对象,通过该对象派发action
import store from "./store";

// 创建类组件,实现页面效果
class Counter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            // 将store中的最新的state赋给当前组件的state
            // getState():获取最新的state tree
            // count是在store.js文件中定义的,初始值为0
            number: store.getState().count,
        }
    }

    componentDidMount() { // 页面加载完成后执行该函数
        // 对store的state进行订阅
        // store.subscribe可以实现订阅,同时该方法会返回一个函数,用于取消订阅
        // 设置this.unSubScribe为当前类的属性,表示可以取消订阅的对象
        this.unSubScribe = store.subscribe(() => {
            this.setState({
                number: store.getState().count
            },()=>{
                this.forceUpdate();
            })
        })
    }


    componentWillUnmount() {
        // 在组件卸载时取消订阅
        this.unSubScribe();
        // this.unSubScribe2();
    }

    render() {
        return (
            <div>
                {/*页面上显示当前状态下的数值*/}
                <p>number:{this.state.number}</p>
                {/*当点击“加1”按钮时派发行为action:ADD*/}
                <button onClick={() => store.dispatch({type: "ADD"})}>加1</button>
                &nbsp;&nbsp;
                <button onClick={() => store.dispatch({type: "MINUS"})}>减1</button>
            </div>
        );
    }
}

export default Counter;


    1. 定义 action
    1. reducer 初始化 store,并定义 reducer
    1. 创建store: import {createStore} from 'redux'; 定义 reducer, const store = createStore(reducer);
    1. getState 获取store 中的state: store.getState
    1. subscribe 订阅store: store.subscribe
    1. dispatch action 派发action 到 reducer: store.dispatch