useReducer 状态显然不是反应性的

useReducer state is apparently not reactive

我有一个想法来构建一个有点像 Vuex 的 React 商店。我想要一个包装器组件接收一个 object 的值,并使用 useContext 提供一个 object 返回,将 stateset 附加到每个键object.

例如,如果您将应用包装在 <StoreProvider value={{ color: 'red' }}> 之类的内容中,您可以在任何 child 中写入 const { color } = useContext(StoreContext),将其值与 color.state 一起使用,然后调用color.set('blue') 全局更改该值。

我在商店写了 best-attempt useReducer:

import React, {
    createContext,
    useContext,
    useReducer
} from 'react'

const StoreContext = createContext(null)
export const useStoreContext = () => useContext(StoreContext)

export const StoreProvider = ({ children, value }) => {
    
    const reducer = (state, action) => {
        state[action.type].state = action.payload
        return state
    }

    Object.keys(value).map(key => {
        let state = value[key]
        value[key] = {
            state: state,
            set: (setter) => dispatch({ type: key, payload: setter })
        }
    })
 
    const [store, dispatch] = useReducer(reducer, value)

    return (
        <StoreContext.Provider value={store}>
            {children}
        </StoreContext.Provider>
    )
}

还有一个小演示,看看它是否有效:

import React from 'react'
import ReactDOM from 'react-dom'

import { StoreProvider, useStoreContext } from './store'

const App = () => {
  const { color } = useStoreContext()

  return (
    <>
    <h1>{color.state}</h1>
    <button onClick={() => color.set('yellow')}>Change color</button>
    <button onClick={() => console.log(color)}>Log</button>
    </>
  )
}

ReactDOM.render(
  <StoreProvider value={{
    color: 'orange'
  }}>
    <App />
  </StoreProvider>,
  document.getElementById('root')
)

在上面,<h1> 正确呈现为 'orange',但当 set('yellow') 为 运行 时不会更新为 'yellow'。但是,如果我记录颜色,state 已更新为 'yellow'——我猜这意味着状态不是反应性的。

我是不是真的做了什么蠢事?我对 React 很陌生,之前从未使用过 useReducer

我认为在这种情况下,只要要管理的状态不是那么复杂,您就可以使用 useState 挂钩来简化您的逻辑:

export const StoreProvider = ({ children, value }) => {
  const { color } = value;

  const [currentColor, setCurrentColor] = useState(color);

  return (
    <StoreContext.Provider value={{ currentColor, setCurrentColor }}>
      {children}
    </StoreContext.Provider>
  );
};

这里是 App 组件:

const App = () => {
  const { currentColor, setCurrentColor } = useStoreContext();

  return (
    <>
      <h1>{currentColor}</h1>
      <button onClick={() => setCurrentColor('yellow')}>Change color</button>
      <button onClick={() => console.log(currentColor)}>Log</button>
    </>
  );
};

我觉得你错过了 React 中非常重要的一点,你永远不应该改变状态对象。 state[action.type].state = action.payload是状态突变。在突变之上,您只需 return 相同的状态对象。为了使 React 状态更新正常工作,您必须 return new object references.

const reducer = (state, action) => {
  return {
    ...state,                // <-- shallow copy state
    [action.type]: {
      ...state[action.type], // <-- shallow copy nested state
      state: action.payload,
    }
  };
}

您还滥用了 array.map 回调;你真的应该使用 forEach 是你正在迭代一个数组并发出副作用。 array.map 被认为是纯函数。

Object.keys(value).forEach(key => {
  let state = value[key]
  value[key] = {
    state: state,
    set: (setter) => dispatch({ type: key, payload: setter })
  }
});

我无法使您的初始 value 映射正常工作,似乎它正在做一些额外的属性嵌套,这些属性与您在 UI 中访问或更新的方式不完全匹配.我建议使用以下方法来计算您的初始减速器状态值。迭代传递给上下文的 value 对象的键值对数组,减少为具有相同键和值的对象,并且 setter 正确嵌套。

const StoreProvider = ({ children, value }) => {
  const reducer = (state, action) => {
    return {
      ...state,
      [action.type]: {
        ...state[action.type],
        state: action.payload
      }
    };
  };

  const state = Object.entries(value).reduce(
    (state, [key, value]) => ({
      ...state,
      [key]: {
        state: value,
        set: (setter) => dispatch({ type: key, payload: setter })
      }
    }),
    {}
  );

  const [store, dispatch] = useReducer(reducer, state);

  return (
    <StoreContext.Provider value={store}>{children}</StoreContext.Provider>
  );
};