useReducer 中第三个参数的目的是什么?
What's the purpose of the 3rd argument in useReducer?
来自docs:
[init, the 3d argument] lets you extract the logic for calculating the initial state outside the reducer. This is also handy for resetting the state later in response to an action.
代码:
function init(initialCount) {
return { count: initialCount };
}
function reducer(state, action) {
switch (action.type) {
...
case 'reset':
return init(action.payload);
...
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
...
}
为什么我要这样做而不是重复使用常量 initialState
?
const initialState = {
count: 5,
};
function reducer(state, action) {
switch (action.type) {
...
case 'reset':
return initialState;
...
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialState);
...
}
我觉得不那么冗长了。
useReducer 接受可选的第三个参数 initialAction。如果提供,初始操作将在初始渲染期间应用。
例如:
function Counter({ initialCount }) {
const [state, dispatch] = useReducer(reducer, initialState, {
type: "reset",
payload: initialCount
});
如您所见,第三个参数是要执行的初始操作,在初始渲染期间应用。
编辑 2020 年 7 月:React documentation 现在对这个名为 lazy initializer
的参数有了更好的解释。以另一种方式使用此功能可能会由于未记录的影响而导致中断更改。以下回答仍然有效。
据我所知,作为第三个参数的 init
函数是 initialState
.
的变换器
这意味着initialState
不会被用作初始状态,而是作为init
函数的arg。这个的return才是真正的initialState
。在 useReducer
初始化行中避免巨大的参数可能很有用。
/* Here is the magic. The `initialState` pass to
* `useReducer` as second argument will be hook
* here to init the real `initialState` as return
* of this function
*/
const countInitializer = initialState => {
return {
count: initialState,
otherProp: 0
};
};
const countReducer = state => state; // Dummy reducer
const App = () => {
const [countState /*, countDispatch */] =
React.useReducer(countReducer, 2, countInitializer);
// Note the `countState` will be initialized state direct on first render
return JSON.stringify(countState, null, 2);
}
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
我的理解是,惰性初始化是为特殊情况设计的,即初始化状态的代码是内存密集型或 CPU-密集型,因此开发人员希望将状态数据的范围保留在组件内部.
例如,如果您要设计一个 PhotoPane 组件,其中包含一张用于编辑的高清照片。
const PhotoPane = (props) => {
const initialPixelData = loadPhoto(props.photoID);
const [pixelData, dispatch] = useReducer(reducerFunc, initialPixelData);
...
}
以上代码存在严重的性能问题,因为 loadPhoto()
被重复调用。如果你不想每次组件渲染时都重新加载照片,直观的反应是将 loadPhoto(props.photoID)
移出组件。但这会引起另一个问题。您必须将所有照片加载到 Context 或其他地方的内存中,这肯定会造成内存消耗。
所以这是我们引入惰性初始化的时候了。请查看下面的代码。
const PhotoPane = (props) => {
const init = (photoID) => loadPhoto(photoID);
const [pixelData, dispatch] = useReducer(reducerFunc, props.photoID, init);
...
}
init()
函数在第一次调用 useReducer
时只执行一次。
其实useEffect()
hook 也可以达到类似的效果。但是延迟初始化仍然是最直接的解决方案。
我认为理解 useReducer
的一个好方法是以 useState
为例,其中 useState
具有初始值或惰性初始化程序。
import { Dispatch, useReducer } from "react";
export function useStateUsingReducer<S>(initialState: S | (() => S)): [S, Dispatch<S>] {
if (typeof initialState === "function") {
return useReducer(
(state: S, newState: S) => (Object.is(state, newState) ? state : newState),
null as unknown as S,
initialState as () => S
);
} else {
return useReducer(
(state: S, newState: S) => (equals(state, newState) ? state : newState),
initialState
);
}
}
这个更实用的版本是做深度等于 useState
只达到 Object.is
。
import { equals } from "ramda";
import { Dispatch, useReducer } from "react";
export function useDeepState<S>(initialState: S | (() => S)): [S, Dispatch<S>] {
if (typeof initialState === "function") {
return useReducer(
(state: S, newState: S) => (equals(state, newState) ? state : newState),
null as unknown as S,
initialState as () => S
);
} else {
return useReducer(
(state: S, newState: S) => (equals(state, newState) ? state : newState),
initialState
);
}
}
来自docs:
[init, the 3d argument] lets you extract the logic for calculating the initial state outside the reducer. This is also handy for resetting the state later in response to an action.
代码:
function init(initialCount) {
return { count: initialCount };
}
function reducer(state, action) {
switch (action.type) {
...
case 'reset':
return init(action.payload);
...
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
...
}
为什么我要这样做而不是重复使用常量 initialState
?
const initialState = {
count: 5,
};
function reducer(state, action) {
switch (action.type) {
...
case 'reset':
return initialState;
...
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialState);
...
}
我觉得不那么冗长了。
useReducer 接受可选的第三个参数 initialAction。如果提供,初始操作将在初始渲染期间应用。
例如:
function Counter({ initialCount }) {
const [state, dispatch] = useReducer(reducer, initialState, {
type: "reset",
payload: initialCount
});
如您所见,第三个参数是要执行的初始操作,在初始渲染期间应用。
编辑 2020 年 7 月:React documentation 现在对这个名为 lazy initializer
的参数有了更好的解释。以另一种方式使用此功能可能会由于未记录的影响而导致中断更改。以下回答仍然有效。
据我所知,作为第三个参数的 init
函数是 initialState
.
这意味着initialState
不会被用作初始状态,而是作为init
函数的arg。这个的return才是真正的initialState
。在 useReducer
初始化行中避免巨大的参数可能很有用。
/* Here is the magic. The `initialState` pass to
* `useReducer` as second argument will be hook
* here to init the real `initialState` as return
* of this function
*/
const countInitializer = initialState => {
return {
count: initialState,
otherProp: 0
};
};
const countReducer = state => state; // Dummy reducer
const App = () => {
const [countState /*, countDispatch */] =
React.useReducer(countReducer, 2, countInitializer);
// Note the `countState` will be initialized state direct on first render
return JSON.stringify(countState, null, 2);
}
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
我的理解是,惰性初始化是为特殊情况设计的,即初始化状态的代码是内存密集型或 CPU-密集型,因此开发人员希望将状态数据的范围保留在组件内部.
例如,如果您要设计一个 PhotoPane 组件,其中包含一张用于编辑的高清照片。
const PhotoPane = (props) => {
const initialPixelData = loadPhoto(props.photoID);
const [pixelData, dispatch] = useReducer(reducerFunc, initialPixelData);
...
}
以上代码存在严重的性能问题,因为 loadPhoto()
被重复调用。如果你不想每次组件渲染时都重新加载照片,直观的反应是将 loadPhoto(props.photoID)
移出组件。但这会引起另一个问题。您必须将所有照片加载到 Context 或其他地方的内存中,这肯定会造成内存消耗。
所以这是我们引入惰性初始化的时候了。请查看下面的代码。
const PhotoPane = (props) => {
const init = (photoID) => loadPhoto(photoID);
const [pixelData, dispatch] = useReducer(reducerFunc, props.photoID, init);
...
}
init()
函数在第一次调用 useReducer
时只执行一次。
其实useEffect()
hook 也可以达到类似的效果。但是延迟初始化仍然是最直接的解决方案。
我认为理解 useReducer
的一个好方法是以 useState
为例,其中 useState
具有初始值或惰性初始化程序。
import { Dispatch, useReducer } from "react";
export function useStateUsingReducer<S>(initialState: S | (() => S)): [S, Dispatch<S>] {
if (typeof initialState === "function") {
return useReducer(
(state: S, newState: S) => (Object.is(state, newState) ? state : newState),
null as unknown as S,
initialState as () => S
);
} else {
return useReducer(
(state: S, newState: S) => (equals(state, newState) ? state : newState),
initialState
);
}
}
这个更实用的版本是做深度等于 useState
只达到 Object.is
。
import { equals } from "ramda";
import { Dispatch, useReducer } from "react";
export function useDeepState<S>(initialState: S | (() => S)): [S, Dispatch<S>] {
if (typeof initialState === "function") {
return useReducer(
(state: S, newState: S) => (equals(state, newState) ? state : newState),
null as unknown as S,
initialState as () => S
);
} else {
return useReducer(
(state: S, newState: S) => (equals(state, newState) ? state : newState),
initialState
);
}
}