以一个counter计数器组件为例,实现加减和异步加法
3123123123123
实现效果图如下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';
/**
* 创建store
* reducers是什么?你可以查看下面reducer.jsx代码内容
* combineReducers(reducers):是将多个reducer合并一起创建进store
* applyMiddleware(thunkMiddleware):注入一个可以实现异步dispatch的插件,
*/
let store = createStore(combineReducers(reducers),applyMiddleware(thunkMiddleware));
// <Provider>:将store透传进所有子组件内,所有子组件都可以通过 react-dedux的connect获取store
export default () => {
return (
<Provider store={store}>
<Counter/>
</Provider>
);
};
Counter.jsx -- 计数器组件代码
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>当前计数器:{reCounter.value}</p>
{/* 点击后计数器+1 */}
<button onClick={()=>{dispatch( Add(1) )}}>加1</button>
<button onClick={()=>{dispatch( Dec(1) )}}>减1</button>
{/* 点击后计数器会在2秒后 +2 用来模拟接口调用 */}
<button onClick={()=>{ dispatch( AddDelay(2,2000) )}}>
{reCounter.loading?"loading":"加2 延迟2秒触发"}
</button>
</div>
);
}
}
/**
* mapStateToProps的作用是将store传递进来的state,进行筛选后暴露给组件
* 这里仅仅把store里面reCounter这个对象暴露给Countre组件
* 所以Counter内可以通过 this.props.reCounter 获取到这个对象
* state:store传递进来的所有reducer
* return {} 你可以把你需要暴露给组件的对象筛选return,当然也可以直接将整个state return
*/
function mapStateToProps(state) {
return {reCounter:state.reCounter};
}
/**
* connect作用是将store和当前组件进行捆绑
* mapStateToProps 函数作用是处理store传入的state对象然后选择需要暴露给组件用的state对象
*/
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这一层,我这里做了个演示,一来是为了避免使用 dispatch({type:"消息名称",value:""}) 这种难看的写法
二来是为了能统一处理一条消息的封装和发送
/**
* value:增加的值
* time:延迟多少ms触发
*/
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();
})
}
}
常见问题
dispatch后值的更新时机
// 方式一
const onClick=(e)=>{
console.log(reCounter.value);//当前值为1
dispatch({ type: 'COUNTER_ADD', value: 1 });
console.log(reCounter.value);//结果还是1
}
//方式二
const onClick=async (e)=>{
await new Promise(resolve=>setTimeout(resolve,1));
console.log(reCounter.value);//当前值为1
dispatch({ type: 'COUNTER_ADD', value: 1 });
console.log(reCounter.value);//结果变为了2
}
阻塞式dispatch更新值方法
假设我们reducer管理一个叫user的对象{name:"姓名",detail:"我是姓名"}
在组件内有一个方法用来监听name是否发生改变,一旦发生改变就重新设置detail的值
当用户输入name发生变化时需要将detail设置为 我是${name}
一开始你可能想这样处理:
const User=(props)=>{
const onNameChange= async (e)=>{
//更新user下name属性
await dispatch({...props.user,name:e.target.value});
// 此处的监听可能存在于子组件中,这里为了方便展示所以直接下载了dispatch后
await listener_nameChange(e.target.value);
console.log(props.user);
}
//监听到name发生变更后,立刻更新了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打印结果显然最终是detail发生了变更而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}`})
}
// ✅ 正确方式应该在props值发生变更时监听,并且处理
useEffect(()=>{
listener_nameChange(props.user.name)
},[props.user.name)
return (
<div>
<Input onChange={onNameChange} value={props.user.name} />
<Input value={props.user.detail} />
</div>
)
}