React - Redux
# Redux理解
Redux 是一个专门用于做 状态管理的 JS 库(不是 React 插件库)
它可以用在 React、Angular、Vue 等项目中,但基本与 React 配合使用
作用:集中式管理 React 应用中多个组件 共享 的状态
# 什么情况下需要使用Redux
某个组件的状态,需要让其他组件可以随时拿到(共享)
一个组件需要改变另一个组件的状态(通信)
总体原则:能不用就不用,如果不用比较吃力才考虑使用
# Redux的三个核心概念
# action
动作的对象
包含两个属性
type:标识属性,值为字符串,唯一,必要属性
data:数据属性,值任意类型,可选属性
例子:
{type:'ADD',data:{name:'kele',age:18}}
# reducer
用于初始化状态、加工状态
加工时,根据旧的 state 和 action, 产生新的 state 的 纯函数
# store
将 state、action、reducer 联系在一起的对象
如何得到此对象?
// 引入 createStore,专门用于创建 Redux 中最为核心的 store 对象 import {createStore} from 'Redux' // 引入为 Count 组件服务的 reducer import reducer from './reducers' const store = createStore(reducer) export default store
1
2
3
4
5
6此对象的功能
getState(): 得到 state
dispatch(action): 分发 action, 触发 reducer 调用, 产生新的 state
subscribe(listener): 注册监听, 当产生了新的 state 时, 自动调用
# 组件与reducer交互
去除 Count 组件自身的状态
src 下建立:
-Redux -store.js -count_reducer.js //xxx_reducer代表xxx组件的reducer
1
2
3store.js:
- 引入 Redux 中的 createStore 函数,创建一个 store
- createStore 调用时要传入一个为其服务的 reducer
- 记得暴露 store 对象
import {createStore} from 'Redux' // 引入为 Count 组件服务的 reducer import reducer from './reducers' // 暴露 store export default createStore(reducer)
1
2
3
4
5组件的使用
引入 store,用于获取 Redux 中保存状态
使用 store 的 API:dispatch 方法,直接传入 reducer 中加工
参数一:type,reducer 中关联的函数名(需要触发的函数)
参数二:data,操作的数据
increment = ()=>{ store.dispatch(type:'increment',data:value*1)) }
1
2
3reducer.js:
reducer 的本质是一个函数,接收参数:preState、action,返回加工后的状态
reducer 有两个作用:初始化状态,加工状态
reducer 被第一次调用时,是 store 自动触发的
传递的 preState 是 undefined
传递的 action 是:
{type:'@@REDUX/INIT_a.2.b.4}
后面通过 dispatch 传入的 preState 为 Redux 的 state 的数据,action 是要执行数据的行为
技巧:开头定义一个常量,给 preState 指定该默认值,当 preState 为 undefined,返回该常量
/* 1. 该文件是用于创建一个为 Count 组件服务的 reducer,reducer 的本质就是一个函数 2. reducer 函数会接到两个参数,分别为:之前的状态(preState),动作对象(action) */ const initState = 0 // 初始化状态 export default function countReducer(preState=initState,action){ // 从 action 对象中获取:type、data const {type,data} = action // 根据 type 决定如何加工数据 switch (type) { case 'increment': // 如果是加 return preState + data case 'decrement': // 若果是减 return preState - data default: return preState } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18在 index.js 中监测 store 中状态的改变,一旦发生改变重新渲染
<App/>
备注:Redux 只负责管理状态,至于状态的改变(
this.setState={}
)驱动着页面的展示,要靠我们自己写。ReactDOM.render(<App/>,document.getElementById('root')) // 监测 Redux 中状态的改变,如 Redux 的状态发生了改变,那么重新渲染 App 组件 store.subscribe(()=>{ ReactDOM.render(<App/>,document.getElementById('root')) })
1
2
3
4
5
# 组件与同步action、reducer交互
action.js:专门用于创建 action 对象,对 dispatch 传入的值进行封装,固定 type,只需传入 data
细节:如果使用箭头函数简写模式,return 的时对象,需要使用括号
()
,否则默认{}
是函数形式而不是对象形式/* 该文件专门为 Count 组件生成 action 对象 */ export const createIncrementAction = data => ({type:'increment',data}) export const createDecrementAction = data => ({type:'decrement',data})
1
2
3
4
5原先组件的 dispatch 变为
import {createIncrementAction} from './action' store.dispatch(createIncrementAction(value*1))
1
2constant.js:当出现多个常量时,此文件放置 type 值在 Redux 文件夹里
/* 该模块是用于定义 action 对象中 type 类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错 */ export const INCREMENT = 'increment' export const DECREMENT = 'decrement'
1
2
3
4
5此时其他文件的对用常量变为
// reducer 文件 ...... switch (type) { case INCREMENT: // 如果是加 return preState + data case DECREMENT: // 若果是减 return preState - data default: return preState } .... // action 文件: export const createIncrementAction = data => ({type:INCREMENT,data}) export const createDecrementAction = data => ({type:DECREMENT,data}) ......
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 异步action
同步 action 和异步 action 区别:前者返回值是一个对象,后者返回值是一个函数,因为只有函数才能开启异步,如定时器
使用方式:(引入中间库)
明确:延迟的动作不想交给组件自身,想交给 action
何时需要异步 action:想要对状态进行操作,但是具体的数据靠异步任务返回
具体编码:
yarn add Redux-thunk
,并配置在 store 中创建 action 的函数不再返回一般对象,而是一个函数,该函数中写异步任务
异步任务有结果后,分发一个同步的 action 去真正操作数据
备注:异步 action 不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步 action
异步 action 一定会 dispatch 同步 action
代码
store.js:引入中间件,并且在 createStore 时使用
// 引入 createStore,专门用于创建 Redux 中最为核心的 store 对象
import {createStore,applyMiddleware} from 'Redux'
// 引入为 Count 组件服务的 reducer
import countReducer from './count_reducer'
// 引入 Redux-thunk,用于支持异步 action
import thunk from 'Redux-thunk'
// 暴露 store
export default createStore(countReducer,applyMiddleware(thunk))
2
3
4
5
6
7
8
action.js:使用异步 action
import {INCREMENT,DECREMENT} from './constant' // 常量文件名
// 同步a ction,就是指 action 的值为 Object 类型的一般对象
export const createIncrementAction = data => ({type:INCREMENT,data})
export const createDecrementAction = data => ({type:DECREMENT,data})
// 异步 action,就是指 action 的值为函数,异步 action 中一般都会调用同步 action,异步 action 不是必须要用的。
export const createIncrementAsyncAction = (data,time) => {
return (dispatch)=>{
setTimeout(()=>{
dispatch(createIncrementAction(data))
},time)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
记住:如果不安装并且使用中间件,action.js 里的异步 action 代码会 报错。
# Redux和React-Redux区别
- Redux 是 React 官方团队出品,React-Redux 是 Facebook 官方团队出品
- React-Redux 对 Redux 进行再次封装,解耦更明显,分离都明显,功能更齐全
- 使用了 React-Redux,则不需要自己主动监测状态变化,页面渲染
- 使用了 React-Redux,UI组件不能写任何 Redux 的内容,必须使用其父容器组件传来的值(父组件才写 Redux 的内容,并且响应事件,通过 props 传给其子 UI 组件)
# React-Redux基本使用
两个概念:(React-Redux 对使用 Redux 的组件进行再次封装)
UI 组件:不能使用任何 Redux 的 api,只负责页面的呈现、交互等。放在 components 文件夹
容器组件:负责和 Redux 通信,将结果交给 UI 组件。放在 contains 文件夹
如何创建一个容器组件?靠 React-Redux 的 connect 函数
格式:
// 使用 connect()() 创建并暴露一个 Count 的容器组件 connect(mapStateToProps,mapDispatchToProps)(UI组件)
1
2左边括号:两个参数,且参数都必须为函数,右边括号:UI 组件的名称,需 import 引入。
mapStateToProps:映射状态,返回值是一个对象,是一个传递状态(数据)的函数,给 UI 组件。
mapDispatchToProps:映射操作状态的方法,返回值是一个对象,是一个传递操作状态的方法的函数,或者对象,给 reducer 加工。
流程:UI 组件触发事件,事件通过 mapDispatchToProps 传入 reducer 进行加工,然后更新状态,mapStateToProps 通过 props 传更新的状态给 UI 组件,UI 组件直接获取。
备注:容器组件中的 store 是靠 props 传进去的,而不是在容器组件中直接引入(App 是 Count 的父组件,Count 组件有容器组件和 UI 组件),然后 App 组件传 store 给 Count 容器组件,Count 容器组件进行操作后数据传给 CountUI 组件
创建容器组件
App 组件
......
export default class App extends Component {
render() {
return (
<div>
{/* 给容器组件传递store */}
<Count store={store} />
</div>
)
}
}
.....
2
3
4
5
6
7
8
9
10
11
12
Count 容器组件(获取父组件传入的 store,并且使用 dispatch 对 Redux 进行数据加工,后返回给 CountUI 组件)
// 引入 Count 的 UI 组件
import CountUI from '../../components/Count'
// 引入 action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../Redux/count_action'
// 引入 connect 用于连接UI组件与 Redux
import {connect} from 'React-Redux'
/*
1. mapStateToProps 函数返回的是一个对象;
2. 返回的对象中的 key 就作为传递给 UI 组件 props 的 key,value 就作为传递给 UI 组件 props 的 value
3. mapStateToProps 用于传递状态
*/
function mapStateToProps(state){
return {count:state}
}
/*
1.mapDispatchToProps 函数返回的是一个对象;
2.返回的对象中的 key 就作为传递给 UI 组件 props 的 key,value 就作为传递给 UI 组件 props 的 value
3.mapDispatchToProps 用于传递操作状态的方法
*/
function mapDispatchToProps(dispatch){
return {
increment:number => dispatch(createIncrementAction(number)),
decrement:number => dispatch(createDecrementAction(number)),
incrementAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
}
}
// 使用 connect()() 创建并暴露一个 Count 的容器组件
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
CountUI 组件(直接使用 props 发送给父组件自己的事件和参数,然后父组件们执行完操作,重新渲染画面)
export default class Count extends Component {
//加法
increment = ()=>{
const {value} = this.selectNumber
this.props.increment(value*1)
}
... //减法、异步加法
render() {
return (
<div>
<h1>当前求和为:{this.props.count}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
......{/* 减法、异步加法的按钮和事件 */}
</div>
)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
优化:mapDispatchToProps,也可以是一个对象,看下方代码块的简写:
分析:this.props.increment(value * 1)
调用对象的 key:increment,随后使用 createIncrementAction 函数,并且因为调用的时候传入参数 value * 1
,所以把该参数传入 createIncrementAction 函数,该函数随后返回 action 对象,dispatch 方法是 API 自动加入,已经优化
export default connect(
state => ({count:state}),
// mapDispatchToProps 的一般写法
/* dispatch => ({
increment:number => dispatch(createIncrementAction(number)),
decrement:number => dispatch(createDecrementAction(number)),
incrementAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
}) */
// mapDispatchToProps 的简写
{
increment:createIncrementAction,
decrement:createDecrementAction,
incrementAsync:createIncrementAsyncAction,
}
)(Count)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# React-Redux优化
容器组件和 UI 组件整合一个文件
因为容器组件需要引入 UI 组件,然后
connect(a,b)(UI组件)
,为何不直接放在一起,然后无需引入 UI 组件,直接整合一个文件整合一个文件后,只需
export default connect(a,b)(UI组件)
即可,UI 组件因为都在一个文件,内部直接使用
无需自己给容器组件传递 store,给 <App/>
包裹一个 <Provider store={store}>
即可。目的是让 App 所有的后代容器组件都能接收到 store。
原先不优化(在 App 组件)
render() { return ( <div> <Count store={store}/> <Count store={store}/> <Count store={store}/> <Count store={store}/> <Count store={store}/> </div> ) }
1
2
3
4
5
6
7
8
9
10
11优化后(入口文件)
...... import {Provider} from 'React-Redux' ReactDOM.render( /* 此处需要用Provider包裹App,目的是让App所有的后代容器组件都能接收到store */ <Provider store={store}> <App/> </Provider>, document.getElementById('root') ) // 此时App文件为 render() { return ( <div> <Count/> <Count/> <Count/> <Count/> </div> ) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
使用了 React-Redux 后不用再自己检测 Redux 中状态的改变了,容器组件可以自动完成这个工作。下方代码(入口文件里)可以 去掉
// 监测 Redux 中状态的改变,如 Redux 的状态发生了改变,那么重新渲染 App 组件
store.subscribe(()=>{
ReactDOM.render(<App/>,document.getElementById('root'))
})
2
3
4
mapDispatchToProps 也可以简单的写成一个对象,具体内容看 React-Redux 基本使用。
一个组件要和 Redux「打交道」要经过哪几步?(融合 1、3、4 的内容)
定义好 UI 组件,不暴露
引入 connect 生成一个容器组件,并暴露,写法如下:
export default connect(
state => ({key:value}), // 映射状态
{key:xxxxxAction} // 映射操作状态的方法,尽量触发简写形式
)(UI组件)
2
3
4
在 UI 组件中通过 this.props.xxx
读取和操作状态,即调用上方(5)第三行的 key。
# React-Redux数据共享
定义一个 Pserson 组件,和 Count 组件通过 Redux 共享数据
为 Person 组件编写:reducer、action,配置 constant 常量
重点:Person 的 reducer 和 Count 的 Reducer 要使用 combineReducers 进行合并,需要引入合并后的总状态是一个对象
// 引入 createStore,专门用于创建Redux中最为核心的store对象 import {createStore,applyMiddleware,combineReducers} from 'Redux' // 引入为 Count 组件服务的 reducer import countReducer from './reducers/count' // 引入 为 Count 组件服务的 reducer import personReducer from './reducers/person' // 引入 Redux-thunk,用于支持异步 action import thunk from 'Redux-thunk' // 汇总所有的 reducer 变为一个总的 reducer const allReducer = combineReducers({ countReducer:countReducer, countReducer:personReducer }) // 暴露 store export default createStore(allReducer,applyMiddleware(thunk))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17交给 store 的是总 reducer(对象),最后注意在组件中取出状态(state)的时候,不再直接是 state,而是
state.countReducer
和state.personReducer
。如果交给 store 的是一个 value 值,通过 state 取这个 value 值,对象的话通过state.xxx
取 key 得 value
# React-Redux开发者工具的使用
使用开发者工具需要安装第三方库支持:(去 chrome 扩展商城下载)
yarn add Redux-devtools-extension
1store 中进行配置
import {composeWithDevTools} from 'Redux-devtools-extension' const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk))) // 如果没有引入 Redux-thunk const store = createStore(allReducer,composeWithDevTools())
1
2
3
4
# React-Redux最终优化
所有变量名字要规范,尽量触发对象的简写形式。
reducers 文件夹中,编写 index.js 专门用于汇总并暴露所有的 reducer
状态如果是一个数组,对该数组进行操作,不能使用 unshift,push 等方法,这样无法改变数组本身的地址,从而数据无法被监测到,实现更新。Vue 框架则相反,必须使用 unshift,push 等方法,才能监测到数据的改变。