以一個(gè)counter計(jì)數(shù)器組件為例,實(shí)現(xiàn)加減和異步加法
3123123123123
實(shí)現(xiàn)效果圖如下12343:
111231234123444123321232133211231231fchangxujkadfjkfdsakljsdf
jkjasdfljasdfsdafjklsajdf
index.jsx -- 入口
import React from 'react';
import {createStore,combineReducers,applyMiddleware} from 'redux';
import { Provider } from 'react-redux'
import thunkMiddleware from 'redux-thunk'
import * as reducers from './reducer/reducer.jsx';
import Counter from './components/Counter.jsx';
import LoginForm from './components/Login.jsx';
/**
* 創(chuàng)建store
* reducers是什么?你可以查看下面reducer.jsx代碼內(nèi)容
* combineReducers(reducers):是將多個(gè)reducer合并一起創(chuàng)建進(jìn)store
* applyMiddleware(thunkMiddleware):注入一個(gè)可以實(shí)現(xiàn)異步dispatch的插件部逮,
*/
let store = createStore(combineReducers(reducers),applyMiddleware(thunkMiddleware));
// <Provider>:將store透?jìng)鬟M(jìn)所有子組件內(nèi),所有子組件都可以通過(guò) react-dedux的connect獲取store
export default () => {
return (
<Provider store={store}>
<Counter/>
</Provider>
);
};
Counter.jsx -- 計(jì)數(shù)器組件代碼
import React from 'react';
import { connect } from 'react-redux'
import {AddDelay,Add,Dec} from '../reducer/action.jsx';
class Counter extends React.Component{
constructor(props){
super(props);
}
render(){
const {reCounter,dispatch}=this.props;
return (
<div className={'flex-row flex-cross-center form'}>
<p>當(dāng)前計(jì)數(shù)器:{reCounter.value}</p>
{/* 點(diǎn)擊后計(jì)數(shù)器+1 */}
<button onClick={()=>{dispatch( Add(1) )}}>加1</button>
<button onClick={()=>{dispatch( Dec(1) )}}>減1</button>
{/* 點(diǎn)擊后計(jì)數(shù)器會(huì)在2秒后 +2 用來(lái)模擬接口調(diào)用 */}
<button onClick={()=>{ dispatch( AddDelay(2,2000) )}}>
{reCounter.loading?"loading":"加2 延遲2秒觸發(fā)"}
</button>
</div>
);
}
}
/**
* mapStateToProps的作用是將store傳遞進(jìn)來(lái)的state,進(jìn)行篩選后暴露給組件
* 這里僅僅把store里面reCounter這個(gè)對(duì)象暴露給Countre組件
* 所以Counter內(nèi)可以通過(guò) this.props.reCounter 獲取到這個(gè)對(duì)象
* state:store傳遞進(jìn)來(lái)的所有reducer
* return {} 你可以把你需要暴露給組件的對(duì)象篩選return,當(dāng)然也可以直接將整個(gè)state return
*/
function mapStateToProps(state) {
return {reCounter:state.reCounter};
}
/**
* connect作用是將store和當(dāng)前組件進(jìn)行捆綁
* mapStateToProps 函數(shù)作用是處理store傳入的state對(duì)象然后選擇需要暴露給組件用的state對(duì)象
*/
export default connect(mapStateToProps)(Counter)
redcuer.jsx -- 存放所有redcuer
/**
* 處理加減法
*/
export function reCounter(state = {value:0,loading:false}, action) {
let value = typeof action.value === 'undefined' ? 1 : action.value;
switch (action.type) {
case 'COUNTER_LOADING': return {...state, loading:action.value };
case 'COUNTER_ADD': return {...state, value:state.value + value };
case 'COUNTER_DEC': return {...state, value:state.value - value };
default: return state;
}
}
action.jsx -- 存放所有被組件直接使用的action胯盯,為什么需要action這一層,我這里做了個(gè)演示计露,一來(lái)是為了避免使用 dispatch({type:"消息名稱",value:""}) 這種難看的寫法
二來(lái)是為了能統(tǒng)一處理一條消息的封裝和發(fā)送
/**
* value:增加的值
* time:延遲多少ms觸發(fā)
*/
export function AddDelay(value,time) {
return (dispatch, getState) => {
return new Promise(reslove => {
dispatch({type:'COUNTER_LOADING',value:true});
setTimeout(() => {
dispatch({ type: 'COUNTER_ADD', value: value });
dispatch({type:'COUNTER_LOADING',value:false});
reslove();
}, time);
})
}
}
/**
* value:增加的值
*/
export function Add(value) {
return (dispatch, getState) => {
return new Promise(reslove => {
dispatch({ type: 'COUNTER_ADD', value: value });
reslove();
})
}
}
/**
* value:減少的值
*/
export function Dec(value) {
return (dispatch, getState) => {
return new Promise(reslove => {
dispatch({ type: 'COUNTER_DEC', value: value });
reslove();
})
}
}
常見問(wèn)題
dispatch后值的更新時(shí)機(jī)
// 方式一
const onClick=(e)=>{
console.log(reCounter.value);//當(dāng)前值為1
dispatch({ type: 'COUNTER_ADD', value: 1 });
console.log(reCounter.value);//結(jié)果還是1
}
//方式二
const onClick=async (e)=>{
await new Promise(resolve=>setTimeout(resolve,1));
console.log(reCounter.value);//當(dāng)前值為1
dispatch({ type: 'COUNTER_ADD', value: 1 });
console.log(reCounter.value);//結(jié)果變?yōu)榱?
}
阻塞式dispatch更新值方法
假設(shè)我們r(jià)educer管理一個(gè)叫user的對(duì)象{name:"姓名",detail:"我是姓名"}
在組件內(nèi)有一個(gè)方法用來(lái)監(jiān)聽name是否發(fā)生改變博脑,一旦發(fā)生改變就重新設(shè)置detail的值
當(dāng)用戶輸入name發(fā)生變化時(shí)需要將detail設(shè)置為 我是${name}
一開始你可能想這樣處理:
const User=(props)=>{
const onNameChange= async (e)=>{
//更新user下name屬性
await dispatch({...props.user,name:e.target.value});
// 此處的監(jiān)聽可能存在于子組件中,這里為了方便展示所以直接下載了dispatch后
await listener_nameChange(e.target.value);
console.log(props.user);
}
//監(jiān)聽到name發(fā)生變更后票罐,立刻更新了detail的值
const listener_nameChange = async (newValue)=>{
await dispatch({...props.user,detail:`i am ${newValue}`})
}
return (
<div>
<Input onChange={onNameChange} value={props.user.name} />
</div>
)
}
這里的log打印結(jié)果顯然最終是detail發(fā)生了變更而name沒有
解決方法
const User=(props)=>{
onNameChange(e){
//更新user下name屬性
dispatch({...props.user,name:e.target.value});
}
listener_nameChange(newValue){
dispatch({...props.user,detail:`i am ${newValue}`})
}
// ? 正確方式應(yīng)該在props值發(fā)生變更時(shí)監(jiān)聽叉趣,并且處理
useEffect(()=>{
listener_nameChange(props.user.name)
},[props.user.name)
return (
<div>
<Input onChange={onNameChange} value={props.user.name} />
<Input value={props.user.detail} />
</div>
)
}