Redux
Store 数据的管理者和数据的存储者
actionCreators 动作的创建者,发送动作给 reducers
react Components 组件( 用来充当视图层 )
reducers 数据的修改者,返回一个新的 newstate 给store
React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。有两个方面,它没涉及。
- 代码结构
- 组件之间的通信
2013年 Facebook 提出了 Flux 架构的思想,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。
如果你不知道是否需要 Redux,那就是不需要它
只有遇到 React 实在解决不了的问题,你才需要 Redux
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。
- 用户的使用方式非常简单
- 用户之间没有协作
- 不需要与服务器大量交互,也没有使用 WebSocket
- 视图层(View)只从单一来源获取数据
需要使用Redux的项目:
- 用户的使用方式复杂
- 不同身份的用户有不同的使用方式(比如普通用户和管理员)
- 多个用户之间可以协作
- 与服务器大量交互,或者使用了WebSocket
- View要从多个来源获取数据
从组件层面考虑,什么样子的需要Redux:
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
Redux的设计思想:
- Web 应用是一个状态机,视图与状态是一一对应的。
- 所有的状态,保存在一个对象里面(唯一数据源)。
注意:flux、redux都不是必须和react搭配使用的,因为flux和redux是完整的架构,在学习react的时候,只是将react的组件作为redux中的视图层去使用了。
Redux的使用的三大原则:
- Single Source of Truth(唯一的数据源)
- State is read-only(状态是只读的)
- Changes are made with pure function(数据的改变必须通过纯函数完成)
使用Redux框架
1.store通过reducer创建了初始状态
2.view通过store.getState()获取到了store中保存的state挂载在了自己的状态上
3.用户产生了操作,调用了actions 的方法
4.actions的方法被调用,创建了带有标示性信息的action
5.actions将action通过调用store.dispatch方法发送到了reducer中
6.reducer接收到action并根据标识信息判断之后返回了新的state
7.store的state被reducer更改为新state的时候,store.subscribe方法里的回调函数会执行,此时就可以通知view去重新获取state
使用redux实现简单todolist
src/store文件夹内
- src/store/index.js
/*
store
* 用于数据存储
*/
import { createStore } from 'redux'
import reducer from './reducer'
const store = createStore( reducer )
export default store
- src/store/reducers.js
/*
reducer
* reducer总部
* options 中接收的是分片的reducer
*/
import { combineReducers } from 'redux'
import { reducer as todolist } from '../components/todolist/store'
// const reducer= combineReducers( options )
const reducer= combineReducers({
//分片的reducer
todolist
})
export default reducer
src/components/todolist文件夹内
- store.js
/*
它就是分片的数据包
*/
import * as type from './type'
import store from '../../store'
const state = {
todos: [
{
id: 1,
task: '任务一'
},
{
id: 2,
task: '任务二'
}
]
}
export const actionCreators = {
add_todos ( val ) {
const action = {
type: type.ADD_TODOS,
payload: val
}
store.dispatch( action )
}
}
export const reducer = ( previousState = state, action ) => {
const newState = {
...previousState
}
switch (action.type) {
case type.ADD_TODOS:
newState.todos.push({
id: newState.todos.length + 1,
task: action.payload
})
break;
default:
break;
}
return newState
}
- TodoList主组件
import React, { Component } from 'react';
import store from '../../store'
import { actionCreators } from './store'
class TodoList extends Component{
constructor(props) {
super(props)
this.state = {
todos: store.getState().todolist.todos
}
}
renderItem = () => {
return this.state.todos.map( item => {
return (
<li key = { item.id }> { item.task } </li>
)
})
}
add = ( e ) => {
if ( e.keyCode === 13 ) {
actionCreators.add_todos( e.target.value )
e.target.value = ''
}
}
componentDidMount () {
store.subscribe(() => {
this.setState({
todos: store.getState().todolist.todos
})
})
}
render() {
return (
<div>
<input onKeyUp = { this.add } name="" id="" className="btn btn-primary" type="text" defaultValue="" />
<hr/>
<ul>
{ this.renderItem() }
</ul>
</div>
)
}
}
export default TodoList
- type.js
export const ADD_TODOS = 'add_todos'
Reducer必须是一个纯函数:
Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。Reducer不是只有Redux里才有,之前学的数组方法reduce
, 它的第一个参数就是一个reducer
纯函数是函数式编程的概念,必须遵守以下一些约束。
- 不得改写参数
- 不能调用系统 I/O 的API
- 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果
由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。
// State 是一个对象
function reducer(state = defaultState, action) {
return Object.assign({}, state, { thingToChange });
// 或者
return { ...state, ...newState };
}
// State 是一个数组
function reducer(state = defaultState, action) {
return [...state, newItem];
}
最好把 State 对象设成只读。要得到新的 State,唯一办法就是生成一个新对象。这样的好处是,任何时候,与某个 View 对应的 State 总是一个不变(immutable)的对象。
我们可以通过在createStore中传入第二个参数来设置默认的state,但是这种形式只适合于只有一个reducer的时候。
划分reducer:
因为一个应用中只能有一个大的state,这样的话reducer中的代码将会特别特别的多,那么就可以使用combineReducers方法将已经分开的reducer合并到一起
注意:
- 分离reducer的时候,每一个reducer维护的状态都应该不同
- 通过store.getState获取到的数据也是会按照reducers去划分的
- 划分多个reducer的时候,默认状态只能创建在reducer中,因为划分reducer的目的,就是为了让每一个reducer都去独立管理一部分状态
关于action/reducer/store的更多概念,请查看官网
Redux异步
通常情况下,action只是一个对象,不能包含异步操作,这导致了很多创建action的逻辑只能写在组件中,代码量较多也不便于复用,同时对该部分代码测试的时候也比较困难,组件的业务逻辑也不清晰,使用中间件了之后,可以通过actionCreator异步编写action,这样代码就会拆分到actionCreator中,可维护性大大提高,可以方便于测试、复用,同时actionCreator还集成了异步操作中不同的action派发机制,减少编码过程中的代码量
常见的异步库:
- Redux-thunk
- Redux-saga
- Redux-effects
- Redux-side-effects
- Redux-loop
- Redux-observable
- …
基于Promise的异步库:
- Redux-promise
- Redux-promises
- Redux-simple-promise
- Redux-promise-middleware
- …
容器组件(Smart/Container Components)和展示组件(Dumb/Presentational Components)
展示组件 | 容器组件 | |
---|---|---|
作用 | 描述如何展现(骨架、样式) | 描述如何运行(数据获取、状态更新) |
直接使用 Redux | 否 | 是 |
数据来源 | props | 监听 Redux state |
数据修改 | 从 props 调用回调函数 | 向 Redux 派发 actions |
调用方式 | 手动 | 通常由 React Redux 生成 |
使用react-redux
可以先结合context
来手动连接react和redux。
react-redux提供两个核心的api:
- Provider: 提供store
- connect: 用于连接容器组件和展示组件
-
Provider
根据单一store原则 ,一般只会出现在整个应用程序的最顶层。
-
connect
语法格式为
connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)(component)
一般来说只会用到前面两个,它的作用是:
- 把
store.getState()
的状态转化为展示组件的props
- 把
actionCreators
转化为展示组件props
上的方法
- 把
特别强调:
官网上的第二个参数为mapDispatchToProps, 实际上就是actionCreators
只要上层中有Provider
组件并且提供了store
, 那么,子孙级别的任何组件,要想使用store
里的状态,都可以通过connect
方法进行连接。如果只是想连接actionCreators
,可以第一个参数传递为null