useRuducer是useState的升级版本,当状态更新逻辑比较复杂的时候,就应该考虑使用useReudcer,因为useReducer比useState更擅长描述如何更新状态,并且通过使用useReducer的dispatch能减少状态值的传递。
用法:
const [state, dispatch] = useReducer(reducer, initialState);
看一个简单的例子:
const initialState = {
n: 0,
};
const reducer = (state, action) => {
if (action.type === "add") {
/* 规则与useState一样必须返回新的对象,不然变量值不会改变 */
return { n: state.n + action.number };
} else if (action.type === "multi") {
return { n: state.n * action.number };
} else {
throw new Error("unknown type!");
}
};
/* 在函数组件中使用useReducer */
const App = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const onclick1 = () => {
dispatch({ type: "add", number: 1 });
};
const onclick2 = () => {
dispatch({ type: "add", number: 2 });
};
const onclick3 = () => {
dispatch({ type: "multi", number: 2 });
};
return (
<div className="App">
<h1>n:{state.n}</h1>
<button onClick={onclick1}>+1</button>
<button onClick={onclick2}>+2</button>
<button onClick={onclick3}>x2</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
dispatch函数可以通过type的类型值对数据进行处理,尤其是在数据比较复杂的情况下可以避免多次使用setState。
看一个表单提交的例子:
const initialState = {
name: "",
age: 18,
nationality: "汉族",
};
const reducer = (state, action) => {
switch (action.type) {
case "patch":
return { ...state, ...action.formData };
case "reset":
return initialState;
default:
throw new Error("unknown type!");
}
};
/* 在函数组件中使用useReducer */
const App = () => {
const [formData, dispatch] = useReducer(reducer, initialState);
const onSubmit = () => {
alert("你点击了提交按钮");
};
const onReset = () => {
dispatch({ type: "reset" });
};
return (
<form onSubmit={onSubmit} onReset={onReset}>
<div>
<label>
姓名
<input
type="text"
value={formData.name}
onChange={(e) => {
dispatch({ type: "patch", formData: { name: e.target.value } });
}}
/>
</label>
</div>
<div>
<label>
年龄
<input
type="number"
value={formData.age}
onChange={(e) => {
dispatch({ type: "patch", formData: { name: e.target.value } });
}}
/>
</label>
</div>
<div>
<label>
民族
<input
type="text"
value={formData.nationality}
onChange={(e) => {
dispatch({
type: "patch",
formData: { nationality: e.target.value },
});
}}
/>
</label>
</div>
<div>
<button type="submit">提交</button>
<button type="reset">重置</button>
</div>
<hr />
{JSON.stringify(formData)}
</form>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
看一个简单的例子:
const initState = {
n: 0,
m: 0,
p: 0,
};
const reducer = (state, action) => {
switch (action.type) {
case "setN":
return { ...state, n: state.n + action.number };
case "setM":
return { ...state, m: state.m + action.number };
case "setP":
return { ...state, p: state.p + action.number };
default:
throw new Error("unknown type!");
}
};
/* 创建上下文对象--模拟一个Redux的作用域 */
const Context = React.createContext(null);
const App = () => {
const [state, dispatch] = React.useReducer(reducer, initState);
return (
<Context.Provider value={{ state, dispatch }}>
<N />
<M />
<P />
</Context.Provider>
);
};
const N = () => {
const { state, dispatch } = React.useContext(Context);
const addClick = () => {
dispatch({ type: "setN", number: 1 });
};
return (
<div>
<h1>N组件</h1>
<div>n:{state.n}</div>
<div>m:{state.m}</div>
<div>p:{state.p}</div>
<button onClick={addClick}>+1</button>
</div>
);
};
const M = () => {
const { state, dispatch } = React.useContext(Context);
const addClick = () => {
dispatch({ type: "setM", number: 2 });
};
return (
<div>
<h1>M组件</h1>
<div>n:{state.n}</div>
<div>m:{state.m}</div>
<div>p:{state.p}</div>
<button onClick={addClick}>+2</button>
</div>
);
};
const P = () => {
const { state, dispatch } = React.useContext(Context);
const addClick = () => {
dispatch({ type: "setP", number: 3 });
};
return (
<div>
<h1>P组件</h1>
<div>n:{state.n}</div>
<div>m:{state.m}</div>
<div>p:{state.p}</div>
<button onClick={addClick}>+3</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
再看一个封装的比较好的例子:
storeProvider.tsx
import React, { useCallback, useEffect, useReducer } from 'react'
/**
* @description 创建当前模块的context方法,之后在子组件中使用useContext引入
*/
export const createStoreContext = () => {
return React.createContext<IStoreContext>({
store: {},
dispatch: () => { },
action: () => { },
})
}
/**
* @description 创建store的组件,包裹子组件,将store以及dispatch传入当前的树中
* @param { Object } props
* @param { IStoreContextType } props.StoreContext
* @param { React.ReactNode } props.children
* @param { IReducer> } props.reducer
* @param { any } props.initData
* @param { IPersistConfig } [props.persistConfig]
* @returns 包裹了store的组件
*/
export const StoreProvider: StoreProviderType = (props) => {
const { StoreContext, children, reducer, initData = {} } = props
// 生成store,以及dispatch
const [store, dispatch] = useReducer(reducer, initData)
const logDispatch = (args) => {
const { type, payload } = args
console.group('Module Store \n')
console.log('action: ' + type + '\n' + 'payload: ')
console.log(payload)
console.groupEnd()
dispatch(args)
}
useEffect(() => {
return () => {
console.log('Module Store ', store)
}
}, [store])
// action操作方式
const action = useCallback((callback) => {
try {
callback?.(store, dispatch)
console.log('DISPATCH CALLBACK', store)
} catch (error) {
console.error(error.message)
}
}, [])
return <StoreContext.Provider value={{ store, dispatch: logDispatch, action }}>{children}</StoreContext.Provider>
}
使用上面封装的组件:
import { StoreProvider, createStoreContext } from '../../utils/storeProvider'
export const StoreContext = createStoreContext()
export const reducer = (state, action) => {
let _state = state
let { type, payload } = action
switch (type) {
case 'init':
return { ..._state, ...payload }
case 'locationUpdate':
return { ..._state, Location: { ..._state.Location, ...payload } }
case 'bigImageChange':
return { ..._state, BigImageTruck: { ..._state.BigImageTruck, ...payload } }
default:
return _state
}
}
export const initData = {
commonData: {},
Location: {
moduleData: {},
stateData: null,
styleInfoData: {
display: true,
componentType: 'MBLocationCity',
subComponentType: null,
mode: null,
editable: null,
sceneType: null,
},
defaultStateData: null,
},
BigImageTruck: {
moduleData: {},
stateData: null,
styleInfoData: {
display: true,
componentType: 'MBBigImageTruck',
subComponentType: null,
mode: null,
editable: null,
sceneType: null,
},
defaultStateData: null,
}
}
export default ({ children }) => {
return (
<StoreProvider StoreContext={StoreContext} reducer={reducer} initData={initData}>
{children}
</StoreProvider>
)
}
使用:
import ModuleIndex, { StoreContext } from './module'
const PreparePage = () => {
const { store, dispatch } = useContext(StoreContext)
// const LocationData = useMemo(() => store.Location, [store.Location])
}
export default () => (
<ModuleIndex>
<PreparePage />
</ModuleIndex>
)