redux
工作流
dispatch派发action来触发reducer更改state
在redux/store.js下
/*
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入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))
redux/constant.js
/*
该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
redux/count_reducer.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(preState);
//从action对象中获取:type、data
const {type,data} = action
//根据type决定如何加工数据
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'
//同步action,就是指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)
}
}
使用
在components/Count/index.js
import React, { Component } from 'react'
//引入store,用于获取redux中保存状态
import store from '../../redux/store'
//引入actionCreator,专门用于创建action对象
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/count_action'
export default class Count extends Component {
state = {carName:'奔驰c63'}
/* componentDidMount(){
//检测redux中状态的变化,只要变化,就调用render
store.subscribe(()=>{
this.setState({})
})
} */
//加法
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(createIncrementAsyncAction(value*1,500))
// },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>
)
}
}
在App.jsx中
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'))
})
react-redux
用于连接redux和UI组件的一个容器,这个容器由react-redux提供
容器组件相当于一个中介,和redux打交道,通过props和UI组件打交道
在container/Count/index.js里创建容器组件,使用connect连接容器组件和UI组件
//引入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 {
jia:number => dispatch(createIncrementAction(number)),
jian:number => dispatch(createDecrementAction(number)),
jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
}
}
//使用connect()()创建并暴露一个Count的容器组件
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
在components/Count/index.js,可以在以下代码中看出,里面基本没有和redux打交道的代码了。
import React, { Component } from 'react'
export default 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() {
//console.log('UI组件接收到的props是',this.props);
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>
)
}
}
在App.jsx中
import React, { Component } from 'react'
import Count from './containers/Count'
import store from './redux/store'
export default class App extends Component {
render() {
return (
<div>
{/* 给容器组件传递store */}
<Count store={store} />
</div>
)
}
}
这里开始优化上面的代码,优化内容如下
1. 容器组件和UI组件整合一个文件
2. 无需自己给容器组件传递store,给App包裹一个< Provider store={store} >即可。这样App里的每个容器组件都会通信redux
3. 使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。(就是不在使用store.subscribe来订阅store)
4. mapDispatchToProps也可以简单的写成一个对象
这里只写container/Count/index.jsx
import React, { Component } from 'react'
//引入action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/count_action'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
//定义UI组件
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() {
//console.log('UI组件接收到的props是',this.props);
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>
)
}
}
//使用connect()()创建并暴露一个Count的容器组件
export default connect(
state => ({count:state}),
//mapDispatchToProps的一般写法
/* dispatch => ({
jia:number => dispatch(createIncrementAction(number)),
jian:number => dispatch(createDecrementAction(number)),
jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
}) */
//mapDispatchToProps的简写
{
jia:createIncrementAction,
jian:createDecrementAction,
jiaAsync:createIncrementAsyncAction,
}
)(Count)
上面的代码只有一个Count组件,没有完成数据共享,下面我们写个多个组件的数据共享版的
- 定义一个Pserson组件,和Count组件通过redux共享数据
- 为Person组件编写:reducer、action,配置constant常量
- 重点:Person的reducer和Count的Reducer要使用combineReducers进行合并,合并后的总状态是一个对象!!!
- 交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”
目录结构如下:
在其目录结构下的redux/store.js
/*
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入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({
he:countReducer,
rens:personReducer
})
//暴露store
export default createStore(allReducer,applyMiddleware(thunk))
react-redux开发者工具的使用
1. npm install redux-devtools-extension
2. store中进行配置
import {composeWithDevTools} from 'redux-devtools-extension'
const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
react-redux最终版
(1).所有变量名字要规范,尽量触发对象的简写形式。
(2).reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer
其reducers/index.js内容如下
redux/store.js的文件内容如下
redux-toolkit
npm i @reduxjs/toolkit react-redux
npm i redux-devtools -D
使用
在store/fetures/counterSlice.js
import { createSlice } from '@reduxjs/toolkit'
export const counterSlice = createSlice({
name:'counter',
initialState:{
count:1,
title:'redux toolkit pre'
},
reducer:{
increment(state,{ payload }){
state.count = state.count + payload.step // 内置了immutable
},
decrement(state){
state.count -= 1
}
}
})
export const { increment,decrement } = counterlice.actions
export const asyncIncrement = (payload) => (dispatch) => {
setTimeout(() => {
dispatch(increment(payload))
},2000)
}
export default counterSlice.reducer
在store/index.js
import { configureStore } from '@reduxjs/toolkit'
import counterSlice from './fetures/counterSlice'
export default configureStore({
reducers:{
'counter':counterSlice
}
})
在index.js中
import store from './store'
import { Provider } from 'react-redux'
ReactDOM.hydrate(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
这里,我们就创建好了store,我们可以通过redux-devtools在浏览器中查看store
在这里比如在App组件中来使用我们的共享数据store
import { useSelector,useDispatch } from 'react-redux'
import { increment,asyncIncrement } from './store/fetures/counterSlice'
function App() {
const { count } = useSelector(state => state.counter) // state.counter 对应的是store/index.js目录的counter,这里又映射到在store/fetures/counterSlice.js文件里的counterReducer
const dispatch = useDispatch()
return(
<div className='App'>
<Button
onClick={() => {
dispatch(increment({ step:1 }))
}}
>
同步加: {count}
</Button>
<hr />
<Button
onClick={() => {
dispatch(asyncIncrement({ step:2 }))
}}
>
异步加: {count}
</Button>
</div>
)
}
我们实现了最基础的案例
但在一个项目中可能有多个reducer,这里我们在上一个基础案例的基础上,增加一个movie的reducer。
同时,还来学习一个createAsyncThunk的API,来创建异步的action
createAsyncThunk创建一个异步的Action,这个方法被触发的时候会有三个状态
pending 进行中
fulfilled 成功
reject 失败
在store/fetures/movieSlice.js
import { createSlice,createAsyncThunk } from '@reduxjs/toolkit'
import { increment } from './counterSlice'
// 此处就是一个请求数据的函数
const loadMoviesAPI = () =>
fetch('http://www.searchList')
.then(res => res.json());
export const loadData = createAsyncThunk('movie/loadData',async (payload,store) => {
const res = await loadMoviesAPI()
store.dispatch(loadDataEnd([1,2,3]))
return res // 此处的返回结果会在.fulfilled中作为payload的值
})
export const movieSlice = createSlice({
name:'movie',
initialState:{
list:[],
totals:0
},
reducer:{
loadDataEnd(state,{ payload }){
state.list = payload;
state.totals = payload.length;
}
},
// extraReducer里的reducer都是订阅模式,由发布者触发
extraReducer:{
// 可以触发其他slice中的数据关联改变
[increment](state,payload){
state.list.push(1);
state.totals -= 1;
},
[loadData.fulfilled](state,{ payload }){
console.log(payload)
state.list = payload.data.list
},
[loadData.rejected](state,err){
console.log(err)
},
[loadData.pending](state,_){
console.log('进行中')
}
}
})
export const { loadDataEnd } = movieSlice.actions
export default counterSlice.reducer
在store/index.js
import { configureStore } from '@reduxjs/toolkit'
import counterSlice from './fetures/counterSlice'
import movieSlice from './fetures/movieSlice'
export default configureStore({
reducers:{
'counter':counterSlice,
'movie':movieSlice
}
})
在App.js中使用movieReducer
import { useEffect } from 'react'
import { loadData } from './store/features/movieSlice'
function App() {
const dispatch = useDispatch()
// 模拟class组件的componentDidMount
useEffect(() => {
dispatch(loadData())
},[])
return(
<div className='App'>
Hello world
</div>
)
}
在App.js中使用movieReducer
import { useEffect } from 'react'
import { loadData } from './store/features/movieSlice'
function App() {
const dispatch = useDispatch()
// 模拟class组件的componentDidMount
useEffect(() => {
dispatch(loadData())
},[])
return(
<div className='App'>
Hello world
</div>
)
}