# redux

Redux 中,状态是不能直接被修改的,而是通过 Action、Reducer 和 Store 3 部分协作 完成的。具体的运作流程可简单地概括为三步,首先由 Action 说明要执行的动作,然后让 Reducer 设计状态的运算逻辑,最后通过 Store 将 Action 和 Reducer 关联并触发状态的更新, 下面用代码演示这个流程。

function caculate(previousState = {digit: 0}, action) { //Reducer
 let state = Object.assign({}, previousState);
 switch (action.type) {
 case "ADD":
 state.digit += 1;
 break;
 case "MINUS":
 state.digit -= 1;
 }
 return state;
}
let store = createStore(caculate); //Store
let action = { type: "ADD" }; //Action
store.dispatch(action); //触发更新
store.getState(); //读取状态

# redux基础知识

TIP

  • redux import{createStore,combineReducers,compose,applyMiddleware} from 'reudx'
    • store=createStore(reducer) => 创建store
      1. store.getState() => 获取仓库数据
      2. unsubscribe=store.subscribe(listener)=> 订阅监听函数,返回卸载函数
        • unsubscribe()卸载监听事件
      3. store.dispatch(action) 派发action,reducer函数去匹配对应的actiontype返回新值
      4. (state=stateDefault,action)=>{} reducer return newstate
    • actionTypes&actionCreators统一管理type和actionCreators,防止书写错误的bug
    • combineReducers:拆分reducer
    • compose
    • applyMiddleware:中间件需要的api
    	let reducer=combineReducers({
    		china:todos,
    		counter
    	})
    

Redux 的设计思想:

  1. Web 应用是一个状态机,视图与状态是一一对应的。
  2. 所有的状态,保存在一个对象里面。

谷歌插件 redux devtools

redux = reducer + flux

cnpm install --save redux

# redux组成

  1. Store:保存数据的地方,可把它看成一个容器。整个应用只有一个 Store。Redux提供 createStore 这个函数,用来生成Store。
  2. State:Store对象包含所有数据。这种数据集合,就叫做 State。当前时刻的 State,可以通过 store.getState() 拿到。
import { createStore } from 'redux';
const store = createStore(fn);
const state = store.getState();
  1. Action:State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。Action 是一个对象。其中的 type 属性是必须的,表示 Action 的名称。
const action = {
  type: 'ADD_TODO',
  payload: 'Learn Redux'
};

Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action。它会运送数据到 Store。

  1. Action Creator:View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator。
const ADD_TODO = '添加 TODO';

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

const action = addTodo('Learn Redux');
  1. store.dispatch() :store.dispatch()是 View 发出 Action 的唯一方法。
import { createStore } from 'redux';
const store = createStore(fn);

store.dispatch({
  type: 'ADD_TODO',
  payload: 'Learn Redux'
});
store.dispatch(addTodo('Learn Redux'));
  1. Reducer:Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
const reducer = function (state, action) {
  // ...
  return new_state;
};
const defaultState = 0;
const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case 'ADD':
      return state + action.payload;
    default: 
      return state;
  }
};

const state = reducer(1, {
  type: 'ADD',
  payload: 2
});

上面代码中,reducer函数收到名为ADD的 Action 以后,就返回一个新的 State,作为加法的计算结果。其他运算的逻辑(比如减法),也可以根据 Action 的不同来实现。

实际应用中,Reducer 函数不用像上面这样手动调用,store.dispatch方法会触发 Reducer 的自动执行。为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法。

import { createStore } from 'redux';
const store = createStore(reducer);

上面代码中,createStore接受 Reducer 作为参数,生成一个新的 Store。以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。

为什么这个函数叫做 Reducer 呢?因为它可以作为数组的reduce方法的参数。请看下面的例子,一系列 Action 对象按照顺序作为一个数组。

const actions = [
  { type: 'ADD', payload: 0 },
  { type: 'ADD', payload: 1 },
  { type: 'ADD', payload: 2 }
];

const total = actions.reduce(reducer, 0); // 3

上面代码中,数组actions表示依次有三个 Action,分别是加0、加1和加2。数组的reduce方法接受 Reducer 函数作为参数,就可以直接得到最终的状态3。

  1. 纯函数:Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。

纯函数是函数式编程的概念,必须遵守以下一些约束。

  • 不得改写参数
  • 不能调用系统 I/O 的API
  • 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果

由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。

// State 是一个对象
function reducer(state, action) {
  return Object.assign({}, state, { thingToChange });
  // 或者
  return { ...state, ...newState };
}

// State 是一个数组
function reducer(state, action) {
  return [...state, newItem];
}
  1. store.subscribe() :Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。
import { createStore } from 'redux';
const store = createStore(reducer);

store.subscribe(listener);

显然,只要把 View 的更新函数(对于 React 项目,就是组件的render方法或setState方法)放入listen,就会实现 View 的自动渲染。

store.subscribe方法返回一个函数,调用这个函数就可以解除监听。

let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
);

unsubscribe();

createStore方法还可以接受第二个参数,表示 State 的最初状态。这通常是服务器给出的。

let store = createStore(todoApp, window.STATE_FROM_SERVER)

上面代码中,window.STATE_FROM_SERVER就是整个应用的状态初始值。注意, 如果提供了这个参数,它会覆盖 Reducer 函数的默认初始值。

# store创建

在src目录下创建一个store目录

编写index.js

import {createStore} from 'redux'
import reducer from './reducer.js'

const store =createStore(
reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()//谷歌插件redux配置
); 

export default store; 

编写reducer.js

const stateDefault={
	inputValue:"",
	list:[1,2,3,4]
}
export default (state=stateDefault,action)=>{
	return state
}

在组件中引用

import store from './store'
this.state=store.getState()

TIP

 import store from './store'
 React.Component.prototype.$store = store;// 不推荐
 //在组件中就可以this.state=this.$store.getState()

# action reducer

当需要修改redux里的值时,需要配置一个action对象

  • store.dispatch(action)//通知redux值需要改变
  • store.subscribe(this.changestore)//订阅改变的内容
  • action包括传送的值和type
  const action = {
      type:'delete_value',
      value:this.state.list[i]
    }
    console.log(action)
    store.dispatch(action)//通知redux值需要改变
	
	//reducer.js
	const stateDefault={
	inputValue:"",
	list:[1,2,3,4]
}
//state是之前的值,而action中的value是现在变化的值
export default (state=stateDefault,action)=>{
    if(action.type=="delete_value"){
        const newState=JSON.parse(JSON.stringify(state));
        newState.list.splice(action.value,1)
        return newState
    }
}
//通过修改state,然后页面会自动调用钩子函数
。。。。。。
 this.changestore=this.changestore.bind(this)
 store.subscribe(this.changestore)
 ......
 changestore(){
    this.setState(store.getState())
  }
	

# actionTypes.js

创建一个actionTypes.js 目的就是为了防止输错type类型时界面不报错,很难查证

export const CHANGE_INPUT_VALUE='change_input_value';
export const ADDLIST_VALUE='addlist_value';
export const DELETE_VALUE='delete_value';

在reducer.js和action对象中也是用这个常量,不过要记得先引入

import {CHANGE_INPUT_VALUE,ADDLIST_VALUE,DELETE_VALUE} from './store/actionTypes.js'
const action ={
        type:CHANGE_INPUT_VALUE,
        value:e.target.value
      }
      store.dispatch(action)
 //reducer.js
 import {CHANGE_INPUT_VALUE,ADDLIST_VALUE,DELETE_VALUE} from './actionTypes.js'
 const stateDefault={
	inputValue:"",
	list:[1,2,3,4]
}
export default (state=stateDefault,action)=>{
    if(action.type==CHANGE_INPUT_VALUE){
        const newState=JSON.parse(JSON.stringify(state));
        newState.inputValue=action.value;
        return newState
    }
	return state
}

# 统一管理action:actionCreators

创建一个actionCreators.js

import {CHANGE_INPUT_VALUE,ADDLIST_VALUE,DELETE_VALUE} from './actionTypes.js'
export const getInputValue=(value)=>({
    type:CHANGE_INPUT_VALUE,
    value
})
//action 统一管理
import {getInputValue} from './store/actionCreators.js'
const action=getInputValue(e.target.value)
  • subscribe是不可或缺的一步,要不然无法进行更新页面变化
import React,{Component} from 'react'
import store from './store/index.js'
export default class Redux1 extends Component{
	constructor(){
		super()
		this.state = {...store.getState(),val:''}
	}
	
	render(){
		return (<div>
		
		<div>{this.state.f1}</div>
		<input value={this.state.val} onChange={this.changeClick}/>
		<button onClick={this.change}>btn</button>
		</div>)
	}
	change = ()=>{
		let action ={
			type:'change',
			text:this.state.val
		}
		store.subscribe(this.changestore)
		store.dispatch(action)
		
	}
	changeClick = (e)=>{
		this.setState((state)=>{
			return{
				val:e.target.value
			}
		})
	}
	changestore=()=>{
		this.setState({...store.getState(),val:''})
	}
}

# combineReducers

当业务应用变得复杂,就需要对 reducer 函数进行拆分,拆分后的每一块独立负责管理 state 的一部分。

combineReducers() 辅助函数的作用就是:把一个由多个不同 reducer 函数作为 value 的 object 合并成为一个总的 reducers 函数。然后可以对这个 reducers 调用 createStore()。

合并后的 reducers 可以调用各个子 reducer,并把他们的结果合并成一个 state 对象。state 对象的结构由传入的多个 reducer 的 key 决定。

当然也可以如果不想使用导出的子reducer名字,那么可以自己去自定义名字

//reducers/counter.js
export default function counter(state = 0, action) {
	switch (action.type) {
		case 'INCREMENT':
			return state + 1
		case 'DECREMENT':
			return state - 1
		default:
			return state
	}
}
//reducers/todos.js

export default function todos(state = [], action) {
	switch (action.type) {
		case 'ADD_TODO':
			return state.concat([action.text])
		default:
			return state
	}
}
import {createStore} from 'redux'

import { combineReducers } from 'redux'
import todos from './todo'
import counter from './counter'

let reducer=combineReducers({
	china:todos,
	counter
})

let store = createStore(reducer)
export default store;
import React,{Component} from 'react'
import store from './store/index.js'
export default class Redux1 extends Component{
	constructor(){
		super()
		console.log(store.getState())
		//{china: Array(0), counter: 0}
		//重命名todo这个子reducer
	}
	
	render(){
		return (<div>
		
		<div>redux2</div>
		
		</div>)
	}
	
}

replaceReducer(nextReducer):更新 Store 中的 Reducer 函数,在实现 Redux 热加载时可能会用到。

# redux type重名问题

vuex有module概念,而redux可以模拟手写module,主动在子reducers加上比如是wei.js文件的名字,如dispatch({type:'wei/add'})

combineReducers (opens new window) redux (opens new window)

最后更新: 12/2/2024, 8:39:41 AM