React-更新reducer内部的无状态数据



我正在实现一个管理会话中所有消息的上下文。

为了降低我的算法的复杂性,我决定使用Map";sectionsRef";用于访问O(1(中的一些内容。

这个映射需要在reducer的逻辑中更新,在这里我更新有状态的数据,以便同步两者。

export function MessagesProvider({ children }) {
const [messages, dispatch] = useReducer(messagesReducer, initialState);
const sectionsRef = useMemo(() => new Map(), []);
const addMessages = (messages, unshift = false) => {
dispatch(actionCreators.addMessages(messages, unshift));
};
const addMessage = (message) => addMessages([message]);
const deleteMessage = (messageId) => {
dispatch(actionCreators.deleteMessage(messageId));
};
const value = useMemo(() => ({
messages,
addMessages,
deleteMessage,
// eslint-disable-next-line react-hooks/exhaustive-deps
}), [messages]);
return (
<MessagesContext.Provider value={value}>
{children}
</MessagesContext.Provider>
);
}

正如你所看到的,我在初始化Map时使用useMemo,以防止由于重新渲染而重新初始化。

将其作为有效载荷传递给我的reducer操作是否正确?

const addMessages = (messages, unshift = false) => {
dispatch(actionCreators.addMessages(messages, unshift, sectionsRef)); <---
};

为了简化我的问题,假设这是真实的代码:

//
// Reducer action
//
function reducerAction(state, messages, sectionsRef, title) {
state.push(...messages);
sectionsRef.set(title, state.length - 1);
}
//
// Context code
//
const state = [];
const firstMessagesSection = [{ id: 1 }];
const secondMessagesSection = [{ id: 1 }, { id: 2 }]
const sectionsRef = new Map();
reducerAction(state, firstMessagesSection, sectionsRef, "first section");
reducerAction(state, secondMessagesSection, sectionsRef, "second section");
console.log(state);
console.log(sectionsRef.get("second section"));

我之所以这么问,是因为我读到我们不应该在减速器逻辑中产生副作用。。。那么,如果我需要将映射与状态同步,我应该怎么做呢?

将其作为有效载荷传递给我的reducer操作是否正确?

否:减速器必须是纯函数。

Redux使用一个简短的列表来描述减速器,我认为这个列表非常有用:

Reducers规则​

我们之前说过减速器必须始终遵循一些特殊规则

  • 它们应该只根据stateaction参数计算新的状态值
  • 他们不允许修改现有的state。相反,他们必须通过复制现有的state并对复制的值进行更改,使不可变更新
  • 它们不能进行任何异步逻辑或其他"异步"操作;副作用">

第二项和第三项一起描述纯函数,第一项只是特定于Redux的约定。

在您的示例中,您违反了纯函数的两条规则:

  • state.push(...messages)改变状态(而不是创建一个新数组并返回它(,以及
  • 通过修改外部作用域中的变量执行副作用:sectionsRef.set(title, state.length - 1)

此外,您似乎从未使用过Map(如何在程序中访问它?(。它应该包含在您的上下文中,您可以简单地在组件外部定义它(它的标识永远不会改变,因此不会导致重新渲染(。

以下是如何重构代码以实现目标:

保持减速机数据纯净:

// store.js
export function messagesReduer (messages, action) {
switch (action.type) {
case 'ADD': {
const {payload, unshift} = action;
return unshift ? [...payload, ...messages] : [...messages, ...payload];
}
case 'DELETE': {
const {payload} = action;
return messages.filter(m => m.id !== payload);
}
}
}
export const creators = {};
creators.add = (messages, unshift = false) => ({type: 'ADD', payload: messages, unshift});
creators.delete = (id) => ({type: 'DELETE', payload: id});
export const sections = new Map();

在将操作分派到相关状态的同时更新Map,方法是将这些操作组合到一个函数中:

// MessagesContext.jsx
import {
createContext,
useCallback,
useMemo,
useReducer,
} from 'react';
import {
creators,
messagesReduer,
sections,
} from './store';
export const MessagesContext = createContext();
export function MessagesProvider ({ children }) {
const [messages, dispatch] = useReducer(messagesReducer, []);
const addMessages = useCallback((title, messages, unshift = false) => {
dispatch(creators.add(messages, unshift));
sections.set(title, messages.length);
}, [creators.add, dispatch, messages]);
const addMessage = useCallback((title, message, unshift = false) => {
dispatch(creators.add([message], unshift));
sections.set(title, messages.length);
}, [creators.add, dispatch, messages]);
const deleteMessage = useCallback((id) => {
dispatch(creators.delete(id));
}, [creators.delete, dispatch]);
const value = useMemo(() => ({
addMessage,
addMessages,
deleteMessage,
messages,
sections,
}), [
addMessage,
addMessages,
deleteMessage,
messages,
sections,
]);
return (
<MessagesContext.Provider value={value}>
{children}
</MessagesContext.Provider>
);
}

使用上下文:

// App.jsx
import {useContext} from 'react';
import {MessagesContext, MessagesProvider} from './MessagesContext';
function Messages () {
const {
// addMessage,
// addMessages,
// deleteMessage,
messages,
// sections,
} = useContext(MessagesContext);
return (
<ul>
{
messages.map(({id}, index) => (
<li key={id}>Message no. {index + 1}: ID {id}</li>
))
}
</ul>
);
}
export function App () {
return (
<MessagesProvider>
<Messages />
</MessagesProvider>
);
}

附加说明:

  • 确保您的依赖项列表(例如useMemo等(是详尽的。这些棉绒警告有助于防止你犯错误。一般来说,你永远不应该压制他们