1 Redux概述
中文文档: http://www.redux.org.cn/
Github: https://github.com/reactjs/redux
1.1 Redux简介
Redux 是一个专门用于做状态管理 的JS库(不是react插件库)。它专门为React.js这门框架设计,但并不是只能用于react,可以用于任何界面库。 它的作用是集中式管理react应用中多个组件共享 的状态。
安装:
npm i redux
使用场景 :
- 某个组件的状态,需要让其他组件可以随时拿到(共享)
- 一个组件需要改变另一个组件的状态(通信)
总体原则 :能不用就不用,如果不用比较吃力才考虑使用
使用Redux的原因:
React仅仅是一个视图层的框架,解决的是数据与模板的渲染问题,但是并没有提供数据的状态管理方案,这在大型项目中是一个非常大的痛点 。
凡是复杂一些的应用都会有下图左边组件树的结构,非父子组件传递数据需要层层传递,如果项目稍微复杂,即使可以开发,但也变得不可维护。因此Redux应运而生。让Redux统一管理数据,任何组件对数据的操作,都交给redux管理,这样就大大提高了开发和维护的效率。
Redux用与不用的区别
1.2 Redux工作流程
首先React组件会在 Redux 中派发一个 action 方法,通过调用 store.dispatch 方法,将 action 对象派发给 store ,当 store 接收到 action 对象时,会将先前的 state 与传来的 action 一同发送给 reducer ,reducer 在接收到数据后,进行数据的更改,返回一个新的状态给 store ,最后由 store 更改 state 。
2 Redux三大核心概念
2.1 Action
action 是把数据从应用传到 store 的有效载荷 ,是 store 中唯一的数据来源,一般我们会通过调用 store.dispatch() 将 action 传到 store
我们需要传递的 action 是一个动作对象,它包含2个属性:
type :标识属性,值为字符串, 唯一, 必要属性data :数据属性,,值类型任意, 可选属性
例如,这里我们暴露了一个用于返回一个 action 对象的方法,我们调用它时,会返回一个 action 对象
export const createIncrementAction = data => ({
type: INCREMENT,
data
})
2.2 Reducer
在 reducer 中,我们需要指定状态的操作类型,要做怎样的数据更新,reducer还会根据 action 的指示,对 state 进行对应的操作,然后返回操作后的 state 。总结:reducer 用于初始化状态及加工状态( 根据 action 更新 state )
如下,我们对接收的 action 中传来的 type 进行判断,更改数据,返回新的状态
export default function countReducer(preState = initState, action) {
const { type,data } = action;
switch (type) {
case INCREMENT:
return preState + data
case DECREMENT:
return preState - data
default:
return preState
}
}
2.3 Store
前面我们了解了action 来描述“发生了什么”,以及使用 reducer 来根据 action 更新 state 的用法。 而store 就是将state 、action 、reducer 联系在一起的对象
store 是 Redux 的核心,可以理解为是 Redux 的数据中台,我们可以将任何我们想要存放的数据放在store 中,在我们需要使用这些数据时,我们可以从中取出相应的数据。
Store 的作用如下:
-
维持应用的 state; -
getState() (https://www.redux.org.cn/docs/api/Store.html#getState):获取 state -
dispatch(action) :分发action,触发reducer 调用,产生新的state -
subscribe(listener) : 注册监听,当产生了新的state 时, 自动调用 -
具体编码: store.getState()
store.dispatch({type:'INCREMENT', number})
store.subscribe(render)
3 Redux API
3.1 createStore()
createStore() :创建包含指定reducer 的store 对象
3.2 applyMiddleware()
applyMiddleware() :应用上基于redux 的中间件(插件库)
3.3 combineReducers()
combineReducers() :合并多个reducer 函数
4 使用redux编写应用
效果图
4.1 代码实现
redux/store.js :该文件专门用于暴露一个store对象,整个应用只有一个store对象
import { createStore } from "redux";
import countReducer from "./count_reducer.js";
export default createStore(countReducer);
redux/count_reducer.js:该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数 reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
import {INCREMENT,DECREMENT} from './constant'
const initState = 0
export default function countReducer(preState=initState,action){
const {type,data} = action
switch (type) {
case INCREMENT:
return preState + data
case DECREMENT:
return preState - data
default:
return preState
}
}
redux/count_action.js:该文件专门为Count组件生成action对象
import {INCREMENT,DECREMENT} from './constant'
export const createIncrementAction = data => ({type:INCREMENT,data})
export const createDecrementAction = data => ({type:DECREMENT,data})
redux/constant.js:该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
Count/index.jsx
import React, { Component } from 'react'
import store from '../../redux/store'
import {createIncrementAction,createDecrementAction} from '../../redux/count_action'
export default class Count extends Component {
increment = ()=>{
const {value} = this.selectNumber
store.dispatch(createIncrementAction(value*1))
}
decrement = ()=>{
const {value} = this.selectNumber
store.dispatch(createDecrementAction(value*1))
}
incrementIfOdd = ()=>{
const {value} = this.selectNumber
const count = store.getState()
if(count % 2 !== 0){
store.dispatch(createIncrementAction(value*1))
}
}
incrementAsync = ()=>{
const {value} = this.selectNumber
setTimeout(()=>{
store.dispatch(createIncrementAction(value*1))
},500)
}
render() {
return (
<div>
<h1>当前求和为:{store.getState()}</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>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/store'
ReactDOM.render(<App/>,document.getElementById('root'))
store.subscribe(()=>{
ReactDOM.render(<App/>,document.getElementById('root'))
})
4.2 总结
store.js:
- 引入redux中的createStore函数,创建一个store
- createStore调用时要传入一个为其服务的reducer
- 记得暴露store对象
count_reducer.js:
- reducer的本质是一个函数,接收:
preState ,action ,返回加工后的状态 - reducer有两个作用:初始化状态,加工状态
- reducer被第一次调用时,是store自动触发的,传递的preState是
undefined , 传递的action是:{type:'@@REDUX/INIT_a.2.b.4}
在index.js中监测store中状态的改变,一旦发生改变重新渲染<App/>
备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。
5 Redux 三大原则
原则一:单向数据流 ,整个 Redux 中,数据流向是单向的
UI 组件 —> action —> store —> reducer —> store
原则二:state 只读 ,在 Redux 中不能通过直接改变 state ,来控制状态的改变,如果想要改变 state ,则需要触发一次 action。通过 action 执行 reducer
原则三:纯函数执行 ,每一个reducer 都是一个纯函数,不会有任何副作用,返回是一个新的 state,state 改变会触发 store 中的 subscribe
6 redux-thunk异步编程
redux默认是不能进行异步处理的,某些时候应用中需要在redux中执行异步任务(ajax, 定时器),那么就需要使用异步中间件redux-thunk
6.1 redux-thunk的使用
redux-thunk的安装:
npm install redux-thunk
count_action.js:该模块专门为Count组件生成action对象
import {INCREMENT,DECREMENT} from './constant'
export const createIncrementAction = data => ({type:INCREMENT,data})
export const createDecrementAction = data => ({type:DECREMENT,data})
export const createIncrementAsyncAction = (data,time) => {
return (dispatch)=>{
setTimeout(()=>{
dispatch(createIncrementAction(data))
},time)
}
}
store.js:该模块专门用于暴露一个store对象,整个应用只有一个store对象
import {createStore,applyMiddleware} from 'redux'
import countReducer from './count_reducer'
import thunk from 'redux-thunk'
export default createStore(countReducer,applyMiddleware(thunk))
6.2 总结
明确 :延迟的动作不想交给组件自身,想交给action
何时需要异步action :想要对状态进行操作,但是具体的数据靠异步任务返回。
具体编码 :
- npm install redux-thunk,并配置在store中
- 创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。
- 异步任务有结果后,分发一个同步的action去真正操作数据。
备注 :异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。
7 react-redux
react-redux :一个React插件库,专门用来简化React应用中使用redux
7.1 react-redux中组件分类
UI组件:
- 只负责 UI 的呈现,不带有任何业务逻辑
- 通过props接收数据(一般数据和函数)
- 不使用任何 Redux 的 API
- 一般保存在components文件夹下
容器组件:
- 负责管理数据和业务逻辑,不负责UI的呈现
- 使用 Redux 的 API
- 一般保存在containers文件夹下
react-redux模型图
7.2 react-redux相关API
-
Provider :让所有组件都可以得到state数据 -
connect :用于包装 UI 组件生成容器组件 -
mapStateToprops :将外部的数据(即state对象)转换为UI组件的标签属性 -
mapDispatchToProps :将分发action的函数转换为UI组件的标签属性
7.3 基本使用
安装react-redux
npm install react-redux
components/Count/index.jsx:UI组件
import React, { Component } from 'react'
export default class Count extends Component {
increment = ()=>{
const {value} = this.selectNumber
this.props.jia(value*1)
}
decrement = ()=>{
const {value} = this.selectNumber
this.props.jian(value*1)
}
incrementIfOdd = ()=>{
const {value} = this.selectNumber
if(this.props.count % 2 !== 0){
this.props.jia(value*1)
}
}
incrementAsync = ()=>{
const {value} = this.selectNumber
this.props.jiaAsync(value*1,500)
}
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>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
containers/Count/index.js:容器组件
import CountUI from '../../components/Count'
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/count_action'
import {connect} from 'react-redux'
function mapStateToProps(state){
return {count:state}
}
function mapDispatchToProps(dispatch){
return {
jia:number => dispatch(createIncrementAction(number)),
jian:number => dispatch(createDecrementAction(number)),
jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
}
}
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
App.jsx:给容器组件传递store
import React, { Component } from 'react'
import Count from './containers/Count'
import store from './redux/store'
export default class App extends Component {
render() {
return (
<div>
{}
<Count store={store} />
</div>
)
}
}
7.4 总结
明确两个概念:
- UI组件:不能使用任何 redux 的api,只负责页面的呈现、交互等。
- 容器组件:负责和 redux 通信,将结果交给UI组件。
如何创建一个容器组件:靠 react-redux 的 connect 函数
connect(mapStateToProps,mapDispatchToProps)(UI组件) mapStateToProps : 映射状态,返回值是一个对象mapDispatchToProps : 映射操作状态的方法,返回值是一个对象
备注:
- 容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
- mapDispatchToProps,也可以是一个对象
7.5 代码优化
优化1 :简写mapDispatchToProps
export default connect(
state => ({count:state}),
{
jia:createIncrementAction,
jian:createDecrementAction,
jiaAsync:createIncrementAsyncAction,
}
)(CountUI)
优化2: Provider
容器组件可以检测redux中的状态改变,并渲染页面,所以不需要在index.js中检测了,不要在App.jsx中给子组件传递store了
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/store'
import {Provider} from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
优化3 :整合UI组件和容器组件
每个组件两个文件夹太麻烦了,直接整合在一起就好了
7.6 数据共享版
import React, { Component } from 'react'
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/count_action'
import {connect} from 'react-redux'
class Count extends Component {
state = {carName:'奔驰c63'}
increment = ()=>{
const {value} = this.selectNumber
this.props.jia(value*1)
}
decrement = ()=>{
const {value} = this.selectNumber
this.props.jian(value*1)
}
incrementIfOdd = ()=>{
const {value} = this.selectNumber
if(this.props.count % 2 !== 0){
this.props.jia(value*1)
}
}
incrementAsync = ()=>{
const {value} = this.selectNumber
this.props.jiaAsync(value*1,500)
}
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>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
export default connect(
state => ({count:state}),
{
jia:createIncrementAction,
jian:createDecrementAction,
jiaAsync:createIncrementAsyncAction,
}
)(Count)
总结
-
容器组件和UI组件整合一个文件 -
无需自己给容器组件传递store,给包裹一个 即可。 -
使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。 -
mapDispatchToProps 也可以简单的写成一个对象 -
一个组件要和redux“打交道”要经过哪几步?
-
定义好UI组件—不暴露 -
引入connect生成一个容器组件,并暴露,写法如下: connect(
state => ({key:value}),
{key:xxxxxAction}
)(UI组件)
-
在UI组件中通过this.props.xxxxxxx读取和操作转态
8 redux使用扩展
8.1 使用redux调试工具Redux Dev Tools
安装chrome浏览器插件Redux Dev Tools
8.2 下载工具依赖包
npm install redux-devtools-extension
在store中进行配置
import {composeWithDevTools} from 'redux-devtools-extension'
const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
8.3 纯函数
reducer 要求是一个纯函数,所以操作数组的时候,不能用push之类的方法
纯函数定义:
- 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)
- 必须遵守以下一些约束
- 不得改写参数数据
- 不会产生任何副作用,例如网络请求,输入和输出设备
- 不能调用
Date.now() 或者Math.random() 等不纯的方法
9 最终版代码
注意:
- 所有变量名字要规范,尽量触发对象的简写形式。
- reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer
containers/Count/index.jsx
import React, { Component } from 'react'
import {
increment,
decrement,
incrementAsync
} from '../../redux/actions/count'
import {connect} from 'react-redux'
class Count extends Component {
increment = ()=>{
const {value} = this.selectNumber
this.props.increment(value*1)
}
decrement = ()=>{
const {value} = this.selectNumber
this.props.decrement(value*1)
}
incrementIfOdd = ()=>{
const {value} = this.selectNumber
if(this.props.count % 2 !== 0){
this.props.increment(value*1)
}
}
incrementAsync = ()=>{
const {value} = this.selectNumber
this.props.incrementAsync(value*1,500)
}
render() {
return (
<div>
<h2>我是Count组件,下方组件总人数为:{this.props.renshu}</h2>
<h4>当前求和为:{this.props.count}</h4>
<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>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
export default connect(
state => ({
count:state.count,
personCount:state.persons.length
}),
{increment,decrement,incrementAsync}
)(Count)
containers/Person/index.jsx
import React, { Component } from 'react'
import {nanoid} from 'nanoid'
import {connect} from 'react-redux'
import {addPerson} from '../../redux/actions/person'
class Person extends Component {
addPerson = ()=>{
const name = this.nameNode.value
const age = this.ageNode.value*1
const personObj = {id:nanoid(),name,age}
this.props.addPerson(personObj)
this.nameNode.value = ''
this.ageNode.value = ''
}
render() {
return (
<div>
<h2>我是Person组件,上方组件求和为{this.props.count}</h2>
<input ref={c=>this.nameNode = c} type="text" placeholder="输入名字"/>
<input ref={c=>this.ageNode = c} type="text" placeholder="输入年龄"/>
<button onClick={this.addPerson}>添加</button>
<ul>
{
this.props.persons.map((p)=>{
return <li key={p.id}>{p.name}--{p.age}</li>
})
}
</ul>
</div>
)
}
}
export default connect(
state => ({
persons:state.persons,
count:state.count
}),
{addPerson}
)(Person)
redux/actions/count.js
import { INCREMENT, DECREMENT } from "../constant";
export const increment = (data) => ({ type: INCREMENT, data });
export const decrement = (data) => ({ type: DECREMENT, data });
export const incrementAsync = (data, time) => {
return (dispatch) => {
setTimeout(() => {
dispatch(increment(data));
}, time);
};
};
redux/action/person.js
import { ADD_PERSON } from "../constant";
export const addPerson = (personObj) => ({ type: ADD_PERSON, data: personObj });
redux/reducers/count.js
import { INCREMENT, DECREMENT } from "../constant";
const initState = 0;
export default function countReducer(preState = initState, action) {
const { type, data } = action;
switch (type) {
case INCREMENT:
return preState + data;
case DECREMENT:
return preState - data;
default:
return preState;
}
}
redux/reducers/person.js
import { ADD_PERSON } from "../constant";
const initState = [{ id: "001", name: "tom", age: 18 }];
export default function personReducer(preState = initState, action) {
const { type, data } = action;
switch (type) {
case ADD_PERSON:
return [data, ...preState];
default:
return preState;
}
}
redux/reducers/index.js
import { combineReducers } from "redux";
import count from "./count";
import persons from "./person";
export default combineReducers({
count,
persons,
});
redux/constant.js
export const INCREMENT = "increment";
export const DECREMENT = "decrement";
export const ADD_PERSON = "add_person";
redux/store.js
import { createStore, applyMiddleware } from "redux";
import reducer from "./reducers";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";
export default createStore(
reducer,
composeWithDevTools(applyMiddleware(thunk))
);
App.jsx
import React, { Component } from 'react'
import Count from './containers/Count'
import Person from './containers/Person'
export default class App extends Component {
render() {
return (
<div>
<Count/>
<hr/>
<Person/>
</div>
)
}
}
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import store from "./redux/store";
import { Provider } from "react-redux";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
展示
项目运行打包
npm run build
npm i serve -g
serve -s build
|