学习文档
1)英文文档: Redux - A predictable state container for JavaScript apps. | Redux
2)中文文档: Redux中文文档
3)Github: GitHub - reduxjs/redux: Predictable state container for JavaScript apps
redux是什么
1)redux是一个专门用于做状态管理的JS库(不是react插件库)。
2)它可以用在react, angular, vue等项目中, 但基本与react配合使用。
3)作用: 集中式管理react应用中多个组件共享的状态。
多个组件之间状态共享,A组件将数据放入redux,然后其他组件可以从redux取得数据。
什么情况下需要使用redux
1)某个组件的状态,需要让其他组件可以随时拿到(共享)。
2)一个组件需要改变另一个组件的状态(通信)。
3)总体原则:能不用就不用, 如果不用比较吃力才考虑使用。
1)组件根据要做的事,组件自己封装action对象;或者组件根据要做的事委托Action Creators封装一个action对象。 action是js中的一个对象 ,包含type、data两个属性。
2)调用dispatch(action) 方法,分发action对象。
3)Store是全局指挥调度者。dispatch方法将action交给Store,然后Store安排Reducer具体执行。Reducer需要初始化
4)执行结果返回给组件
action
(1)是一个对象,通过函数返回此对象。
(2)action对象包含2个属性
type:标识属性, 值为字符串, 唯一, 必要属性
data:数据属性, 值类型任意, 可选属性
(3)例子:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }
reducer
(1)本质:是一个纯函数
(2) 作用:用于初始化状态、加工状态。
(3)加工时,根据旧的state和action, 产生新的state。
store
(1)将state、action、reducer联系在一起的对象
(2)如何得到此对象?
import {createStore} from 'redux' import reducer from './reducers' const store = createStore(reducer)
(3)此对象的功能?
1)getState(): 得到state
2)dispatch(action): 分发action, 触发reducer调用, 产生新的state
3)subscribe(listener): 注册监听, 当产生了新的state时, 自动调用
项目github地址:GitHub - MeBetterMan/click-redux
项目效果如下
不使用redux,使用纯react实现。
一个组件内,建立select button标签,改变state属性即可实现。 如下为VSCode选择多行快捷键
按住alt,用鼠标左键点击,可以出现多个光标,输入的代码可以在光标处同时增加。
按shift+alt,再使用鼠标拖动,也可以出现竖直的列光标,同时可以选中多列。
选中一段文字,按shift+alt+i,可以在每行末尾出现光标
按住Ctrl + Alt,再按键盘上向上或者向下的键,可以使一列上出现多个光标。
光标放在一个地方,按ctrl+shift+L或者ctrl+f2,可以在页面中出现这个词的不同地方都出现光标。
使用store、reducer,不使用Action Creators
1)安装redux
yarn add redux
2)新建redux文件夹
src目录下建立components、redux文件夹,分别存放不同用处的js文件。
每个需要redux的组件,都需要对应一个reducer,所以命名为:组件名xx_reducer,意为专门为组件xx服务的reducer;全局一个store,命名为store.js即可。
3)编写store.js
- 引入redux中的createStore函数,创建一个store
- createStore调用时要传入一个为其服务的reducer
- 记得暴露store对象
import { createStore } from 'redux' import count_reducer from './count_reducer' export default createStore(count_reducer)
4)编写count_reducer.js
reducer本质为一个function,接收preState、action对象两个参数,返回一个newState参数。preState为前次调用的结果。reducer会被store统一管理。
reducer用于初始化和后续多次调用。第一次调用是store自动触发的,此时preState值为undefined,传递的action是:{type:'@@REDUX/INIT_a.2.b.4},必须返回一个默认值。后续调用store会将前次调用的结果传给preState。
prevState = initalState 为默认值,如果preState为undefined或者调用时没有传值,那么使用默认值;否则是否传入的值。
const initalState = 0 export default function count_reducer(prevState = initalState, action) { const { type, data } = action switch (type) { case 'add': return prevState + data * 1 case 'sub': return prevState - data * 1 default: return initalState } }
5)编写组件
store.getState()获取store目前存储的状态数据值。
store.dispatch({ type: 'add', data: value }) 将action对象分发给reducer让其执行,返回新状态存储到store。store存储的状态发生改变,不能够重新渲染页面。必须调用setState方法才可。 this.render()直接这样调用也不会渲染页面。
store.subscribe(()=>{}) 会在每次store存储的数据发生变化时,执行回调函数。如下代码中,空调setState方法,重新渲染了组件。如果不空调,store状态改变不会显示到页面中。redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写
import React, { Component } from 'react' import store from '../../redux/store' export default class Count extends Component { add = () => { const value = this.selRef.value store.dispatch({ type: 'add', data: value }) } componentDidMount() { store.subscribe(() => { this.setState({}) }) } render() { return (
) } }总和是:{store.getState()}
6)触发页面渲染
如果有很多个组件都需要reducer,那么触发重新渲染就需要在每个组件里面都空调setState方法,显然不合适。那么可以在入口index.js文件直接渲染整个全体页面。由于diff算法的存在,也不会全部都渲染,只会渲染有改变的标签,故不会浪费太多性能。
ReactDOM.render(
, document.getElementById('root') ); store.subscribe(() => { ReactDOM.render( , document.getElementById('root') ); })
完整版就是增加生成action对象的文件。
1)JavaScript小技巧
对象定义简写方式
let data = 'hello' {type:'inc',data:data} 可以省略data属性值,简写为 {type:'inc',data}
箭头函数简写
箭头函数语法 (arg1,args2...)=>{ ... return ... } 1.如果1个参数 可以省略小括号 2.如果代码段仅一个返回值 可以圣罗return data=> return data + 1 但如果返回的是对象,需要小括号包括 data =>({data:data+1}) 如果下面写法 大括号会被视为代码段的大括号 data =>{data:data+1})
如果一个常数值被多个文件使用,那么就新建一个constant.js文件模块,然后定义常量值,目的是便于管理和防止程序员写错单词;如果一个文件内多处使用,就在文件最开始定义常量。
export const INCREMENT = 'increment' export const DECREMENT = 'decrement'
2)新建action文件
此文件作用:通过函数生成生成某个容器组件的action对象。如下图的count_acton.js
每个action针对一个组件,所以命名为 组件名xx_action_creators.js。每个用到action对象的地方直接调用此文件定义的方法返回action即可。
下图中的constant.js文件用来专门放置常量值。
import { INCREMENT } from "./constant" export const createIncrementAction = data => ({ type: INCREMENT, data }) export const createDecrementAction = data => ({ type: '', data })
action可以是一个JS对象,也可以是一个function。是对象时就是同步action,是function时就是异步action。
为什么用异步action?异步的动作不想交给组件执行,想交给action
何时需要异步action?想要对状态进行操作,但是具体的数据靠异步任务返回。
备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action。
(1) 安装 redux-thunk
异步action只有安装了redux-thunk中间件,store才可以处理异步action方法。
yarn add redux-thunk
(2)在store.js中添加中间件
createStore()添加第二个参数,让store可以处理action为function的参数。
import { createStore, applyMiddleware } from 'redux' import count_reducer from './count_reducer' import thunk from 'redux-thunk' export default createStore(count_reducer, applyMiddleware(thunk))
(3)编写异步action方法
1)action方法返回函数
2)函数内一般会有一个异步任务。
3)然后异步任务完成后,调用 同步action的方法 比如下面的createIncrementAction就是一个同步的action方法
//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。 export const createIncrementAsyncAction = (data,time) => { return (dispatch)=>{ setTimeout(()=>{ dispatch(createIncrementAction(data)) },time) } }
(4)store调用异步action
异步action其实就是让store执行异步action返回的function方法。此function等待异步任务执行完成,然后再调用同步action的方法。
store.dispatch(createIncrementAyncAction(value*1,500))
redux为第三方公司开发,react-redux为Facebook开发,专门用来使用redux的一个工具。上图中的redux就是第五小节使用的redux。
需要将组件拆分为容器组件和UI组件,UI组件和redux通过容器组件作为桥梁进行数据交换。
1).UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
2).容器组件:负责和redux通信,将结果交给UI组件。
(1)建立文件夹
src下新建containers、components文件夹,分别存放容器组件、UI组件。就是以前的一个组件根据功能分为了两个组件,且container组件被react-redux自动设置为ui组件的父组件。文件夹下还是各个组件文件夹。同一个组件容器组件和UI组件名字一样。
(2)容器组件
容器组件作用是建立redux和UI组件的联系。靠react-redux 的 connect函数创建一个容器组件。 容器组件需要将redux中保存的state和操作state的方法传递给UI组件供其使用,也就是mapStateToProps,mapDispatchToProps这两个方法。
关键方法:connect(mapStateToProps,mapDispatchToProps)(UI组件)
-mapStateToProps:映射状态,返回值是一个对象。将store.getState()方法返回值以props形式传给UI组件使用。
-mapDispatchToProps:映射操作状态的方法。返回值是一个对象,对象的value为一个函数,函数中调用dispatch方法分发action对象;也可以直接是一个对象,对象的value为创建action对象的函数。
-mapStateToProps、mapDispatchToProps就是用户自定义函数,用来获取状态和操作状态,注意其在connet方法中的顺序。名字可任意,这两个名字是react-redux官方推荐。
备注1: 容器组件的核心就是获取UI组件、获取redux的store。UI组件import导入,store由使用容器组件的父组件通过props传递,store就是上节编写的store。传过来store以后,react-redux会自动将状态和dispatch方法映射给用户自定义函数的形参让用户操作store。
备注2:mapDispatchToProps,可以是一个函数,也可以是一个对象。是对象时,value为创建action的方法,react-redux自动调用store的dispatch方法,只需要将对象的value设置为action即可;是函数时,需要函数形参接收dispatch函数,然后手写分发action对象。
备注3:mapStateToProps,mapDispatchToProps函数会通过参数形式自动接收react-redux管理的store传递过来的状态值和分发action对象的dispatch方法。如下代码所示
备注4:mapStateToProps,mapDispatchToProps函数的返回对象的KV会作为props的KV传递给UI组件。react-redux实现的。
//引入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用于传递状态 4.此函数调用者为react-redux 其调动时已经自动将store.getState()方法返回值赋值给了state形参 */ function mapStateToProps(state){ return {count:state} } /* 1.mapDispatchToProps函数返回的是一个对象; 2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value 3.mapDispatchToProps用于传递操作状态的方法 4.此函数调用者为react-redux 其调动时已经自动将store.dispatch()方法赋值给了dispatch形参 */ function mapDispatchToProps(dispatch){ return { jia:number => dispatch(createIncrementAction(number)), jian:number => dispatch(createDecrementAction(number)), jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)), } } //使用connect()()创建并暴露一个Count的容器组件 //connect()()第一个括号定义两个函数a b a为获取状态的函数 b为设置状态的函数 ;第二个括号为UI组件 export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
(3)容器组件的父组件传递store
App组件时Count容器组件的父组件,通过props传过来store 。store为第五小节定义的store
import React, { Component } from 'react' import Count from './containers/Count' import store from './redux/store' export default class App extends Component { render() { return (
{/* 给容器组件传递store */}) } }(4)UI组件中使用、操作store中保存的状态
//通过mapDispatchToProps方法返回的对象中的key调用修改store状态的方法 increment = ()=>{ const {value} = this.selectNumber this.props.jia(value*1) } //通过mapStateToProps方法返回的对象key获取store存储的状态
当前求和为:{this.props.count}
优化一 mapDispatchToProps为一个对象
connect(mapStateTpProps,mapDispatchToProps)(UI)
mapDispatchToProps可以是对象,对象的value为创建action的函数。react-rex会自动调用store的dispatch方法将action分发。简化为如下:
export default connect( state => ({ count: state }), //通知redux执行加法 ({ jia: createIncrementAction }) )(CountUI)
优化二 不再使用store.subscribe
不使用react-redux时,需要store.subscribe方法感知store存储的状态发生改变,然后重新渲染页面。
使用react-redux后,就不在需要,connect方法自动实现了对store状态的监听,已发生变化,自动渲染UI组件。
store.subscribe(() => { ReactDOM.render(
, document.getElementById('root') ); })
优化三 Provider批量进行store传递
如果很多个容器组件,都通过如下传递,很冗余
可以在入口文件index.js中使用react-redux的Provider,将store={store}传递给 App子组件中所有的容器组件。
步骤 1)引入Provider 2)包裹项目根组件App或者特定容器组件的根组件
import React from 'react'; import ReactDOM from 'react-dom'; import store from './redux/store' import App from './App'; import { Provider } from 'react-redux'; ReactDOM.render(
, document.getElementById('root') );
优化四 容器组件和UI组件定义在一个文件
如果每个容器都分为容器组件一个文件,UI组件一个文件。容器多了以后,文件会很多,不方便管理。
一个文件可以定义多个组件。因此,可以将UI组件直接定义在容器组件的文件内,放在containers文件夹下
import { connect } from 'react-redux' import { createIncrementAction } from '../../redux/count_action' import React, { Component } from 'react' //一个文件完成两个工作 1)定义UI组件 2)connect函数返回容器组件 class CountUI extends Component { add = () => { const { value } = this.selRef this.props.addConnect(value) } render() { return (
) } } //容器组件 export default connect( state => ({ count: state }), //通知redux执行加法 ({ addConnect: createIncrementAction }) )(CountUI)总和是:{this.props.count}
优化后react-redux开发步骤
一个组件要和redux“打交道”要经过哪几步?
0)安装redux 、react-redux 。yarn add redux react-redux
1)新建redux文件夹,定义好store、reducer、action三个文件
一个UI组件对应一个reducer、action,因此如果使用store组件多,需要建立reducers、actions分别存储reducer、action
2)定义好UI组件---不export
3)使用connect()() 创建并暴露一个容器组件,写法如下:
export default connect( state => ({key:value}), //映射状态 是个箭头函数 {key:xxxxxAction} //映射操作状态的方法 是个对象 )(UI组件)
(4) 入口文件根组件使用Provider组件包裹,完成store的注入。
(5)在UI组件中通过this.props.xxxxxxx读取和操作状态
源码GitHub地址 click-redux/9_src_最终版 at master · MeBetterMan/click-redux · GitHub
项目效果
Count组件、Person组件,可以互相访问彼此存在redux中的数据
1) 建立redux、containers文件夹
每一个容器组件都对应一个action和reducer 。所以将redux文件夹分为actions和reducers文件夹。编写person组件的action和reducer,命名就使用对应容器组件的名字。
容器组件放在containers文件夹下。
reducer (count.js)
/* 1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数 2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action) */ import {INCREMENT,DECREMENT} from '../constant' const initState = 0 //初始化状态 export default function countReducer(preState=initState,action){ // console.log('countReducer@#@#@#'); //从action对象中获取:type、data const {type,data} = action //根据type决定如何加工数据 switch (type) { case INCREMENT: //如果是加 return preState + data case DECREMENT: //若果是减 return preState - data default: return preState } }
reducer注意点
(1) reducer一般不使用push unshift等方法。比如下面代码,就不会返回新值,因为redux发现上一次返回的state和本次返回的state是同一个引用地址值。所以认为数据没有改变,就不返回了。因为push方法修改了传入的形参preState,导致reducer不是纯函数,
preState.push('value') return preState
(2)reducer必须是纯函数
一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)。纯函数必须遵守以下一些约束
-- 不得改写函数传入的形参数据
-- 不会产生任何副作用,例如网络请求,输入和输出设备
-- 不能调用Date.now()或者Math.random()等不纯的方法
(3)不同reducer之间的type不能相同,如果不同reducer type相同,当传递此type的action对象时,两个reducer都会执行。(重要)
action (count.js)
/* 该文件专门为Count组件生成action对象 */ import {INCREMENT,DECREMENT} from '../constant' //同步action,就是指action的值为Object类型的一般对象 export const increment = data => ({type:INCREMENT,data}) export const decrement = data => ({type:DECREMENT,data}) //异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。 export const incrementAsync = (data,time) => { return (dispatch)=>{ setTimeout(()=>{ dispatch(increment(data)) },time) } }
2)store里面合并所有reducer
所有reducer都通过combineReducers合并成一个对象,每个reducer作为value,分配一个任意名称的key。相对应的redux里面的state也是一个对象,key为此处定义的key,从而可以针对指定reducer进行state操作。
通过此处的key实现了store同时存储不同组件的状态值。一个项目只需一个store,需要多个action和reducer。
如果reducer很多,都写到store里面进行合并,会和臃肿。可以在reducers文件夹下新建index.js 用来将所有reducer进行合并 ,然后export合并后的allReducers,store引入使用。分文件思想。
store.js
import { createStore, applyMiddleware, combineReducers } from 'redux' import count_reducer from './reducers/count' import person_reducer from './reducers/person' import thunk from 'redux-thunk' const allReducers = combineReducers( { count: count_reducer, person: person_reducer } ) export default createStore(allReducers, applyMiddleware(thunk))
3)UI组件里面暴露容器组件
在UI组件代码里,通过react-redux connect方法取得redux和UI组件的关联,通过两个入参函数来获取store中状态和向store存储状态,入参函数返回的对象的key会通过props传给UI。然后UI组件通过props来存入、提取状态。
//使用connect()()创建并暴露一个Count的容器组件 export default connect( state => ({ count:state.count, personCount:state.persons.length }), {increment,decrement,incrementAsync} )(Count)
4)UI组件调用action方法向redux存入状态
react-redux自动根据传入的action对象,调用dispatch方法,将action传给对应reducer执行。
//加法 increment = ()=>{ const {value} = this.selectNumber this.props.increment(value*1) }
5)state取值
combineReducers参数值是一个对象,那么对应的state也是一个对象 ,state根据combineReducers传入对象的key分别存储状态值
当前求和为:{this.props.count}
6)UI组件 count.js
UI组件中使用store步骤
①通过connect方法将UI组件暴露成容器组件 connect方法通过两个入参函数 将获取store状态和通过action向store存入状态暴露给props,UI通过props的key直接使用
②UI通过props使用 获取状态和存入状态的方法
import React, { Component } from 'react' //引入action import { increment, decrement, incrementAsync } from '../../redux/actions/count' //引入connect用于连接UI组件与redux import {connect} from 'react-redux' //定义UI组件 class Count extends Component { state = {carName:'奔驰c63'} //加法 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() { //console.log('UI组件接收到的props是',this.props); return (
) } } //使用connect()()创建并暴露一个Count的容器组件 export default connect( state => ({ count:state.count, personCount:state.persons.length }), {increment,decrement,incrementAsync} )(Count)我是Count组件,下方组件总人数为:{this.props.renshu}
当前求和为:{this.props.count}
(优化一)reduce文件夹下新增一个index.js 用来合并所有的reducer
react文件夹下index.js, 用于combine所有的reducer供store使用 所有项目此代码比较固定
/* 该文件用于汇总所有的reducer为一个总的reducer */ //引入combineReducers,用于汇总多个reducer import {combineReducers} from 'redux' //引入为Count组件服务的reducer import count from './count' //引入为Person组件服务的reducer import persons from './person' //汇总所有的reducer变为一个总的reducer export default combineReducers({ count, persons })
store.js 代码固定 可复用。支持reducer、开发者工具、异步调用 。
/* 该文件专门用于暴露一个store对象,整个应用只有一个store对象 */ //引入createStore,专门用于创建redux中最为核心的store对象 import {createStore,applyMiddleware} from 'redux' //引入汇总之后的reducer import reducer from './reducers' //引入redux-thunk,用于支持异步action import thunk from 'redux-thunk' //引入redux-devtools-extension import {composeWithDevTools} from 'redux-devtools-extension' //暴露store export default createStore(reducer,composeWithDevTools(applyMiddleware(thunk)))
谷歌商店安装扩展程序 Redux DevTools,用来调试查看store里面存储的状态。
使用步骤
(1)yarn add redux-devtools-extension
(2)store中进行配置
import {composeWithDevTools} from 'redux-devtools-extension'
const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
(3)如下是调试工具截图 可以查看action state等,也可以点击箭头处,直接向store存入一个action对象。