跳到主要内容

Redux入门

什么是 Redux

Redux 是一个 JavaScript 状态容器,提供可预测化的状态管理。不光能用于 React,也能用于其它 JS 库。它的大小只有 2KB 左右,非常得轻量。

Redux 提供了一种将 UI 和数据分离的机制,个人感觉其实和 MVC 差不多

Redux机制

Action

需要注意的时,在 Redux 中Action 是一个对象,表示数据的操作类型和携带的数据本身,将其送往store进行处理。也就是说在 Redux 中不存在propsstate的概念了,所有的数据通过 Action 进行传送

Action 通常具有数据操作类型名称和携带的数据本身,例如添加一条待办事项的 Action 如下:

{
type: "ADD_TODO",
payload: {
id: 1,
value,
},
}

可以使用一个负责生成的action的函数,这样就可以将数据操作完全的从页面剥离开,不用在 UI 组件中去生成 Action,这样做可以使代码层次更清晰;比如点击添加待办事项按钮onClick,通过 Action 生成函数可以产生一个type:"ADD_TODO"的 Action 对象

let todoId = 0;

//创建action
export const addTodo = value => ({
type: 'ADD_TODO',
payload: {
id: ++todoId,
value,
},
});

Reducer

Reducer 时真正负责管理state的纯函数,它负责接受当前state以及 Action,通过判断 Action 的类型,决定执行什么样的操作去修改state并返回新的state

Reducer 可以拆分,最后再通过combineReducers函数将它们合并然后传入 Store 中

Note: 尤其需要注意的一点是Reducer 中修改 state 的操作要保证 immutable,对于深层嵌套的 state,一定要深拷贝

例如,在添加一条代办事项后,组装的 Action 对象会被送到 Reducer 特定的函数这里:

const initState = {
todoList: [],
};

export default function(state = initState, action) {
switch (action.type) {
// 判断Action的操作类型
case 'ADD_TODO': {
const { id, value } = action.payload;
// 返回新的state
return {
todoList: [
...state.todoList,
{
id,
value,
isComplete: false,
},
],
};
}
default:
return state;
}
}

Store

Store 在 Redux 中是一个中心管理器,它使用createStore函数根据特定的 Reducer 生成,负责将 Action 派发到 Reducer,同时将 Reducer 产生的新的stateprops的形式传入组件内部。

import { createStore } from 'redux';
import reducer from '../reducers';

export default createStore(reducer);

页面组件发生交互事件,会产生 Action,而页面组件无法直接将 Action 送往 Reducer 去处理,它需要通过store.dispatch(action)来分发 Action 给 Reducer;而当 Reducer 处理完了以后,Reducer 也无法将state直接给组件,所以组件需要通过store.subscribe(listener)来订阅state的更新。

middleware

middleware 提供的是生成 Action 以后,在到达 Reducer 之前的功能拓展,所以可以使用 middleware 来进行调用异步接口,日志记录等功能开发。

在传统的 Redux 使用过程中,Action 只是一个对象,当组件生成 Action 以后,只能同步传递到 Store 中处理。有了 middleware,就可以在 Action 处理之前执行一些异步操作,让 Action 延迟分发到 Store 的过程,在等到一定延迟以后再将 Action 发送到 Store 去处理。

React-Redux

React Redux 是基于 React 的Context对 Redux 的进一步封装,提供共享的全局状态管理机制。

安装

React Redux 需要配合 Redux 一起使用

yarn add redux react-redux

API

React Redux 使用部分 Redux 的 API,同时封装了自己的 API

Provider

<Provider store={store}>使用 React 的Context提供全局store共享的机制。Context提供的是在跨越组件层级的共享数据管理机制,对于某些需要全局共享的状态,例如登录用户信息,用户主题等,使用Context做到全局共享是最简单的方式。

使用Context的方式如下:

  • 首先在组件外部创建Context,允许后传入一个默认值;然后将顶层组件通过Context.Provider包裹起来,并设置传递的值
import React from 'react';

export const UserContext = React.createContext();
export default function() {
return (
<UserContext.Provider value={'皮皮虾'}>
<Root />
</UserContext.Provider>
);
}
  • 记得生成Context以后要导出,这样其它组件可以通过import获取Context共享的值
  • 其他组件在通过import引入Context以后,可以使用Context.Consumer来订阅 context 的变化,Context.Consumer内部必须是一个函数作为子元素,这个函数接收当前的 context 值,并返回一个 React 节点
<ThemeContext.Consumer>
{value => <input value={value} />}
</ThemeContext.Consumer>

createStore

createStore(reducer,[preloadedState],enhancer)根据 Reducer 创建 Store。

reducer:就是从 Reucer 导出的 reducer 函数,负责接收新的state和 Action;

preloadedState:初始时的 state,一般来说是放在 reducer 中,方便管理;

enhancer:这个参数传入一个组合多个 Store 的高阶函数,返回一个新的产生 Store 的函数

connect

connect(mapStateToProps,mapDispatchToProps,[mergeProps],[options])函数是一个高阶函数,负责将 UI 组件,Action,Reducer 连接起来。

  • mapStateToProps(state,[ownProps]):如果一个组件需要监听 Store 内部state的更新,那么必须传入这个函数。这个函数可以接受两个参数,更新后的state以及其他组件传给当前组件ownProps;在接收到新的state以后,mapStateToProps可以在内部操作返回当前组件需要的state,通过props的形式传递到组件内部

  • mapDispatchToProps(dispatch,ownProps):还记得上文说过的吗,Redux 不存在stateprops的概念传递数据依赖 ActionmapDispatchToProps函数将返回一个对象,对象的每个字段都是一个函数,在调用这些函数的时候就会生成 Action,然后 Action 会被传送到 Store 内部通过 Reducer 进行处理。

    dispatch就是生成 Action 的函数,默认情况下,dispatch 会通过props注入到组件内部,这样组件内部就可以在交互的过程中产生 Action 触发dispatch 事件,然后 Store 就可以获取 Action。

  • mergeProps:额外的传入组件内部的props

  • options:一个额外的参数对象,可以设置以下参数:

    • pure: true:默认值就是true,也就是将对组件内部的state进行一个浅比较,避免不必要的根本更新
    • withRef: false:默认值false,保存一个对被被包含的组件实例的引用,该引用通过 getWrappedInstance() 方法获得。

connect函数会被调用两次:

  • 第一次调用,通过将上述两个函数传入到connect内部以后,connect会生成一个新的高阶函数,新的高阶函数接受组件作为参数,可以生成一个高阶组件
  • 第二次调用会生成一个高阶组件

项目的管理

一般来说,最直观的在项目中使用react-redux的方式是利用一个单独的文件夹redux来集中管理数据,在redux目录中,

  • actions负责生成 Action,这样组件内部可以直接将actions内部导出生成 Action 的函数传递给mapDispatchToProps,然后组件内部就可以通过props获取到生成 Action 的函数,将数据传递给它,例如:
// actions文件夹中负责生成Action的函数
export const addTodo = value => ({
type: 'ADD_TODO',
payload: {
id: ++todoId,
value,
},
});

export const toggleTodo = todoId => ({
type: 'TOGGLE_TODO',
payload: { id: todoId },
});
//组件
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { addTodo } from '../../redux/actions';

class Input extends Component {
state = {
value: '',
};

handleInput = e => {
this.setState({
value: e.target.value,
});
};

handleEnterKeyUp = e => {
if (e.keyCode === 13) {
this.setState({ value: '' });
//获取props注入的生成Action的函数
this.props.addTodo(this.state.value);
}
};

render() {
return (
<input
placeholder="请输入事项"
value={this.state.value}
onChange={this.handleInput}
onKeyUp={this.handleEnterKeyUp}
/>
);
}
}

//直接将生成Action的函数放在对象中传递给connect,简单简洁
export default connect(null, { addTodo })(Input);
  • reducers负责构建操作state的 Reducer,并生成新的state;各个组件或者页面的 Reducer 可以拆分,然后在index.js中统一使用combineReducers组合
const initState = {
todoList: [],
};

/**
* 接收当前state和Action,根据不同的Action执行不同的操作,然后返回的新的state
* @param {Object} state
* @param {Object} action
*/
export default function(state = initState, action) {
switch (action.type) {
case actionType.add_todo: {
const { id, value } = action.payload;
return {
todoList: [
...state.todoList,
{
id,
value,
isComplete: false,
},
],
};
}
case actionType.set_complete: {
const { id } = action.payload;
const newTodoList = [...state.todoList];
const oldIndex = newTodoList.findIndex(item => item.id === id);
let todoItem = newTodoList[oldIndex];
todoItem.isComplete = true;
newTodoList.splice(oldIndex, 1, todoItem);
return {
todoList: newTodoList,
};
}
default:
return state;
}
}
  • store只负责根据reducers文件夹导出的 Reducer 生成 Store
import { createStore } from 'redux';
import reducer from '../reducers';

export default createStore(reducer);
  • 最后由Providerstore进行全局共享
import React from 'react';
import { Provider } from 'react-redux';
import store from './redux/store';

export default function() {
return (
<Provider store={store}>
<Root />
</Provider>
);
}

最后就是这样的目录结构:

redux
├─ actions
│ ├─ actionType.js
│ └─ index.js
├─ reducers
│ └─ index.js
└─ store
└─ index.js