React学习05 - redux
文章目录
- redux工作流程
- redux理解
- redux理解及三个核心概念
- redux核心api
- redux异步编程
- react-redux
- 组件间数据共享
- 纯函数
- redux调试工具
- 项目打包
redux工作流程
redux理解
redux是一个专门用于状态管理的JS库,可以用在react, angular, vue 等项目中。在与react配合使用时,集中式管理react应用中多个组件共享的状态。
当某个组件的状态需要让其他组件随时可以拿到时,当一个组件需要改变另一个组件的状态时,可以使用redux。
为什么不用消息订阅与发布?消息订阅与发布需要组件挂载后才能发布获取消息,当想要不挂载组件,先发布消息,再挂载组件接收消息的时候,就需要redux。
在react中使用redux,需要使用Redux Toolkit(RTK)和react-redux。
redux理解及三个核心概念
根据原理图,例如,当页面需要将一个数据+2,当前该数据为0 。
- Action Creators会创建一个action,把这个action分发(dispatch)给store,我们给action执行的操作自命名为Add,操作的数据为2。
- store接收到action后,将当前的状态0,和action一起交给Reducers,Reducers在原状态0的基础上,执行操作Add,也就是+2,然后返回新状态数据2 。
- Store接收到新状态,页面组件可以通过getState方法获取新状态数据。
当页面初次加载时,是在Reducers内初始化状态的,原状态为undefined,操作为初始化,Reducers可以初始化和加工状态。
三个核心概念:action, reducer, store。
action 包含两个属性,1.type 标识性属性,唯一且必要,值为字符串,2.data 数据属性,可选,值为任意类型。每个组件都可以有自己的ActionCreator。
{type:'自定义操作名称', data:数据}
reducer是一个函数,根据旧的state和action产生新的state。每个组件都可以有自己的reducer。
store是一个对象,将state, action, reducer联系在一起。
import {createStore} from 'redux'
import reducer from './reducers'
const store = createStore(reducer) // 获取store对象
通常集中状态管理的部分会在src下单独创建一个store目录,当建立很多子store模块时,在store目录下创建一个modules文件夹,包含所有子store模块文件,在store文件夹的index.js中将所有子模块组合并导出store。
redux核心api
通过案例代码理解store三个核心API: getState, dispatch, subscribe。在src下新建目录redux,创建store.js和demo_reducer.js两个文件,终端增加redux包。
npm add redux
redux4.0.5
// store.js
// 引入创建store的方法
import {createStore} from 'redux'
// 引入为组件服务的reducer
import demoReducer from './demo_reducer'
// 创建并暴露store
const store = createStore(demoReducer)
export default store
// demo_reducer.js 为Demo组件服务的reducer,本质是函数,返回新状态
const initState = 0; // 初始化状态
export default function demoReducer(preState=initState,action){
const {type,data} = action;
switch(key){
case 'increment':
return preState + data;
case 'decrement':
return preState - data;
default:
return 0; //无指定操作,即为初始化状态操作
}
}
// Demo组件
import store from '../../redux/store'
export default class Demo extends Component{
componentDisMount(){
// 检测redux的状态更新,当前页面的状态随redux中的更新
store.subscribe(()=>{
this.setState({})
})
}
add = ()=>{
store.dispatch({type:'increment',data:2})
}
render(){
return (
<div>
<h1>当前值:{store.getState()}</h1>
<button onClick={this.add}>加2</button>
</div>
)
}
}
// App index.js Demo组件的subscribe可以去掉,放到App里,避免多个组件需要订阅重复代码
store.subscribe(()=>{
ReactDOM.render(<App/>,document.getElementById('root'))
})
以上就完成了一个精简的redux。下面增加ActionCreator,将redux完善。在redux目录下创建demo_action.js。对应修改组件里调用dispatch。
// demo_action.js
export default function createIncrementAction(data){
return {type:'increment',data}
}
// Demo组件
// 引入actionCreator,专门用于创建action对象
import createIncrementAction from '../../redux/demo_action'
// add方法改为
add = ()=>{
store.dispatch(createIncrementAction(2))
}
然后在redux目录下新建constant.js,用来将操作名集中管理,避免写错字符串。对应的修改reducer和action里的操作名。
// constant.js
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';
// demo_reducer.js
import {INCREMENT,DECREMENT} from './constant.js'
switch(key){
case INCREMENT:
return preState + data;
case DECREMENT:
return preState - data;
default:
return 0;
}
// demo_action.js
import {INCREMENT,DECREMENT} from './constant.js'
export default function createIncrementAction(data){
return {type:INCREMENT,data}
}
以上就是完整的redux,有action,store,reducer,组件和constant五个文件内容,其中组件、store、reducer是必须的。
redux异步编程
action一般为一个对象,action也可以为一个函数,当它为对象时,是同步的,当它是函数时,是异步的。异步action应该包含ajax,计时器等异步任务,对象和数组满足不了,所以异步的action要使用函数实现。
// Demo组件
import store from '../../redux/store'
export default class Demo extends Component{
componentDisMount(){
// 检测redux的状态更新,当前页面的状态随redux中的更新
store.subscribe(()=>{
this.setState({})
})
}
asyncAdd = ()=>{
setTimeout(()=>{
store.dispatch(createIncrementAction(2))
},500) // 等待500毫秒后再执行加2,等待动作在组件里,组件等待500毫秒后发送请求去让redux执行一系列操作
}
render(){
return (
<div>
<h1>当前值:{store.getState()}</h1>
<button onClick={this.asyncAdd}>异步加2</button>
</div>
)
}
}
// demo_action.js
import {INCREMENT,DECREMENT} from './constant.js'
export default function createIncrementAction(data){
return {type:INCREMENT,data}
}
以下尝试实现一个函数类型的action,理想store接收到函数类型的action,丢给reducer处理,等待500毫秒后再通过store.dispatch去生成一个对象类型的action,然后reducer执行+2操作。实际会报错,action传递到store处理必须要是对象类型的。
// 修改组件内异步加2的方法
asyncAdd = ()=>{
store.dispatch(createAsyncIncrementAction(2,500)); //组件立即发送请求,让redux去等待500毫秒后再执行一系列操作
}
// 在action文件中增加createAsyncIncrementAction方法,实现异步action
export const createAsyncIncrementAction = (data,time)=>{
return ()=>{
setTimout(()=>{
store.dispatch(createIncrementAction(data))
},time)
} // return的是一个函数类型的action,里面包含异步任务。store接收到函数类型的action,丢给reducer处理不了函数类型的,所以返回报错
}
引入中间件,当返回的action是一个函数包含异步任务时,由中间件处理异步部分,将异步任务完成后生成的对象类型的action传递给store,再去真正操作数据,解决报错。
npm add redux-thunk
// store.js
// 引入创建store的方法
import {createStore,applyMiddleware} from 'redux'
// 引入为组件服务的reducer
import demoReducer from './demo_reducer'
// 引入redux-thunk
import thunk from 'redux-thunk'
// 创建并暴露store
const store = createStore(demoReducer,applyMiddleware(thunk))
export default store
// 在action文件中增加createAsyncIncrementAction方法,实现异步action
export const createAsyncIncrementAction = (data,time)=>{
return ()=>{
setTimout((dispatch)=>{
dispatch(createIncrementAction(data))
},time)
} // return的是一个函数类型的action,里面包含异步任务。store接收到函数类型的action,传递给中间件处理,此时在处理的依旧是store,所以可以传递dispatch函数,免去store.dispatch调用
}
react-redux
react-redux用来连接redux和react组件。
所有的UI组件都应该包含一个在一个容器组件里,是父子关系。
容器组件才能使用redux的API与redux打交道,UI组件不能使用任何redux的API。
容器组件会通过props传给UI组件redux中保存的状态和用于操作状态的方法。
UI组件内不能含有redux,创建容器组件,在容器组件内引入react-redux
npm add react-redux
根据模型图,容器组件要向外连接redux,向内连接UI组件。容器组件通过react-redux连接UI组件,通过store连接redux。
// 容器组件DemoContainer/index.jsx
// 1.引入UI组件
import DemoUI from '../../components/Demo'
// 2.引入react-redux, connect用于连接UI组件和redux
import {connect} from 'react-redux'
// 3.连接UI组件和容器组件
const DemoContainer = connect()(DemoUI); // connect是一个函数,返回值依然是一个函数
export default DemoContainer; // 向外暴露一个容器组件
// 在App内
// 1.引入容器组件,不引入UI组件
import DemoContainer from './containers/DemoContainer'
// 2.引入store
import store from '../../redux/store'
export default class App extends Component{
render(){
return (
<div>
<DemoContainer store={store}/>
</div>
) // 3.连接容器组件和redux,给容器组件传递store
}
}
容器组件向UI组件通过props传值,通过connect函数的第一个参数,connect第一个参数为函数,返回的值作为UI组件的状态。connect第二个参数为函数,返回值作为UI组件操作状态的方法。
// mapStateToProps用于传递状态,返回一个对象,key和value对应props的key value
function mapStateToProps(state){
// return {n:900}; // 相当于<DemoUI n={900} />
return {n:state}
}
// mapDispatchToProps用于传递操作状态的方法,返回一个对象,key和value对应props的key value
function mapDispatchToProps(dispatch){
return {add: (data)=>{
dispatch(createIncrementAction(data)) // 通知redux做加法
}}
}
export default connect(mapStateToProps,mapDispatchToProps)(DemoUI)
容器组件的store是靠props传进去的,而不是引入的。
mapDispatchToProps简化
// ui组件
increment = ()=>{
const value = this.selectNumber;
this.props.jia(value)
}
// 容器组件mapDispatchToProps
const mapDispatchToProps = dispatch=>({
jia:number => dispatch(createIncrementAction(number)),
jian: number => dispatch(createDecrementAction(number))
})
// 继续简化,将mapStateToProps和mapDispatchToProps省略,直接使用connect函数
export default connect(
state => ({n:state}), //mapStateToProps简写
{
jia:createIncrementAction,
jian:createDecrementAction
} // mapDispatchToProps简写
)(DemoUI)
容器组件使用connect连接ui组件和redux,容器组件可以自调用subscribe更新页面,所以可以省略store.subscribe的调用。
可以在App index.js内给所有子组件传递store,在子组件内可以不引入store直接使用
// App index.js
import store from './redux/store'
import {Provider} from 'reatc-redux'
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
UI组件可以写在容器组件的文件内,合并为一个文件,只暴露容器组件。
总结,组件与redux打交道:
- 定义UI组件,不暴露
- 使用connect生成容器组件,并暴露
connect(
state => ({key:value}), // 映射状态
{key:xxxAction} // 映射操作状态的方法
)(UI组件) - 在UI组件内通过this.props.xxx读取和操作状态
// UI组件和容器组件合并
class Demo extends Component{
add = ()=>{
this.props.jiafa(1)
}
render(){
return (
<div>
<h1>和为:{this.props.sum}</h1>
<button onClick={this.add}>点击+1</button>
</div>
)
}
}
export default connect(
state => ({sum:state}),
{jiafa:createIncrementAction}
)(Demo)
组件间数据共享
创建Person组件,创建对应的action, reducer,在store内使用combineReducers把所有组件的reducer合并。
// store.js
import demoRudecer from './reducers/demo'
import personReducer from './reducers/person'
import {combineReducers} from 'redux'
const allReducer = combineReducers({
sum:demoReducer,
persons:personReducer
}) // combineReducers里传递的对象就是store管理的状态对象
// Demo组件里connect使用
state => ({s: state.sum, num: state.persons.length}) // 传递给UI组件使用
纯函数
纯函数只要是同样的输入(实参),必定得到同样的输出(返回)。
纯函数内不能改写参数数据,不能产生副作用(比如不能发送网络请求、不能连接输入输出设备等有可能造成数据丢失的行为),不能调用Date.now或Math.random等返回值不定的方法。
redux的reducer函数必须是一个纯函数。
redux调试工具
引入工具库,可以在浏览器看到redux内的状态
npm add redux-devtools-extension
// store.js
// 引入工具库
import {composeWithDevTools} from 'redux-devtools-extention'
export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
项目打包
执行命令生成build文件夹
npm run build
将生成的build文件夹放到服务器,使用Node或Java搭建服务器,或者使用serve库。
npm i serve -g // 全局安装库
serve a // 以a文件夹为根目录作为服务器运行
serve // 以当前文件为服务器运行