React ContextAPI:为什么商店数据的本地副本会更新?

React ContextAPI: why is a local copy of store data getting updated?

我正在使用 ContextAPI 来管理全局数据状态,以便与其 Provider 中包装的任何页面共享。在一种情况下,我需要复制商店中的内容并在本地进行更改,但不影响其他页面中使用的数据。但是,我的更新是直接更新全局商店数据。

在代码中,tmpStoreActivities.unshift(response); setCurrentActivity(tmpStoreActivities[0]); 正在更新商店的 activities 数组,即使 activities 被重新分配给其他变量两次,我不明白为什么。我是否误解了全局存储值的修改方式?

STORE - 通过 ActivitiesProvider

export const useActivitiesStore = () => {
  const activitiesService = new ActivitiesService();

  const [activitiesState, setActivitiesState] = useState(
    initialActivitiesState
  );

  const { activities } = activitiesState;

   //REDUCER       
   const setActivities = (newActivities) => {
    setActivitiesState((prevState) => ({
      ...prevState,
      ...{ activities: newActivities }
    }));

   const getRecentActivities = useCallback(async (params?: {}) => {
    setIsLoading(true);
    resetActivities();

    try {
      //API call
      const recentActivities = await activitiesService.getRecentActivities(
        params
      );
      setActivities(recentActivities);

      //OPTIONAL USAGE: use this version of the data ONLY when a separate local copy is needed
      return recentActivities;
    } catch (error) {
      setError('Unable to Retrieve Activities');
    } finally {
      setIsLoading(false);
    }
    // disabling eslint because using this as dependency with dependencies causes infinite loop
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  };

   return {
    activitiesState,
    getRecentActivities
  };
};

组件

import { useActivities } from '../../services/ActivitiesProvider';
    
export default function Activity() {
  const { activitiesService, activitiesStore } = useActivities();
  const { activitiesState, getRecentActivities } = activitiesStore;
  const { activities, searchResults, isSearching } = activitiesState;

  const [currentActivity, setCurrentActivity] = useState<IClassificationActivity>(null);
  const [currentActivities, setCurrentActivities] = useState<IClassificationActivity[]>(null);
    
//caching the initial load of activities, so infinite loops don't occur if we getRecentActivities later
  const storeActivities = useRef<IActivity[]>(isSearching ? searchResults : activities).current;

  useEffect(() => {
    //Get all the details for selected activity
    const getActivityDetails = async () => {
      const convertedType: number = type === ActivityType.Website ? 0 : 1;
      const convertedId = parseInt(id);
      try {
        const response = await activityService.getClassificationActivityDetails(
          convertedId,
          convertedType
        );
        if (response) {
          setActivityDetail(response);

          if (storeActivities?.length) {
            //if activities are pre-loaded from the Store, but the current activity does not exist in that array, add it to the front of the array and start the prev/next flow from there
            const currActivity = storeActivities.filter(
              (activity: IActivity) =>
                activity.type === type && activity.id === parseInt(id)
            )[0];
            if (!currActivity) {
              const tmpStoreActivities = storeActivities;
              tmpStoreActivities.unshift(response);
              setCurrentActivity(tmpStoreActivities[0]);
            }
          }
        }
      } catch (error) {
        notificationService.error('Unable to load this Activity');
      } finally {
        setIsLoading(false);
      }
    };
    if (id && id !== 'undefined' && id !== 'null' && type) {
      getActivityDetails();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [id, type, storeActivities, getRecentActivities]);

...
}

ROOT

<ActivitiesProvider>
    <Switch>
      <Route
        path={Routes.Classification(':type', ':id')}
        component={Activity}
        exact
      />
    </Switch>
  </ActivitiesProvider>

上下文 API 用于在组件之间共享数据,而无需传递思想道具。这样做是为了避免道具钻井的问题。

上下文 API 所做的只是共享该信息,为了使您的对象不可变,您应该检查 Object.freeze() or some third party libraries like immutable

当你有一个变量时,在 Javascript 中,当你将它重新分配给另一个变量时,你实际上并没有重新分配它,你只是通过引用指向它。 例如,当你有一个名为 myState 的状态时,它包含一个像这样的对象:

{
    foo: "bar"
}

然后你做这样的事情:

export default function App() {
  const [myState, setMyState] = useState({ foo: "bar" });

  const myVar = myState;

  myVar.foo = "foo";

  console.log(myState); //Output: {foo: "foo"}

  return (
    <div className="App">
      <div></div>
    </div>
  );
}

尽管重新分配,控制台日志的输出会告诉您 foo 的值实际上是“foo”。不过别担心,有一种方法可以防止这种情况,它被称为“传播”。

您可以像这样使用展开运算符复制对象:

const myStateCopy = {...myState};

现在,您不再引用 myState。您可以在此 example. And please refer to the docs 中查看点差的工作原理以获取更多信息

编辑:

您也可以使用数组进行传播。

const myArrayState = [{foo: "bar"}];

const myArrayStateCopy = [...myArrayState];