You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React/Redux解耦UI状态与全局存储状态,解决UI卡顿问题

UI更新与Redux全局存储解耦问题

编辑补充:该问题疑似和UI更新时同步调用API有关。

我现在遇到严重的UI/UX问题:有一组按钮需要快速更新样式,但另外有个组件加载过程耗时很长,现在按钮的更新速度和这个加载慢的组件同步,用户体验特别差。

两个组件依赖同一个Redux的categories切片数据,切片代码如下:

import { createSlice } from "@reduxjs/toolkit";

const initialState = [];

export const categoriesSlice = createSlice({
  name: "categories",
  initialState,
  reducers: {
    categoriesSave: (state, action) => {
      state = [...state, action.payload];
      return state;
    },
    categoriesSaveAll: (state, action) => {
      if (state === action.payload) {
        return state;
      }
      return action.payload;
    },
  },
});

export const { categoriesSave, categoriesSaveAll } =
  categoriesSlice.actions;

export default categoriesSlice.reducer;

我的分类选择组件代码如下:

const CatsModalChild = (props: any) => {
  const reduxCategories = useSelector(
    (state: RootStateOrAny) => state.categories
  );
  const [localCategories, setLocalCategories] = useState<any[]>([]);
  const categories = useCategoriesAPIQuery({ taxon: props.taxon });
  const [selectedCategories, setSelectedCategories] = useState<string[]>([]);

  const categoryChange = (value: any) => {
    let _selectedCategories = [...selectedCategories];

    if (_selectedCategories.indexOf(value) === -1) {
      _selectedCategories.push(value);
    } else {
      _selectedCategories = _selectedCategories.filter(
        (item) => item !== value
      );
    }
    setSelectedCategories(_selectedCategories);
  };

  const onCategoriesSelectionsChange = () => {
    if (categories.status === "fulfilled") {
      let selectedCats = selectedCategories.map((x: any) => x.value);
      let deleteCats = new Set(categories.data.map((x: any) => x.value));
      // @ts-ignore
      const difference = localCategories.filter((x) => !deleteCats.has(x));
      const cats = [...selectedCats, ...difference];
      props.categoriesSaveAll(cats);
    }
  };

  const categoriesMapper = () => {
    let _selectedCategories: any = [];
    for (let cat of localCategories) {
      let catVal = categories.data.filter((item: any) => item.value === cat);
      let selectedCat = catVal[0];
      if (selectedCat) {
        _selectedCategories.push(selectedCat);
      }
    }
    setSelectedCategories(_selectedCategories);
  };

  // 尝试解耦Redux和本地state但未成功
  useEffect(() => {
    const _localCategories = [...reduxCategories];
    setLocalCategories(_localCategories);
  }, []);

  useEffect(() => {
    onCategoriesSelectionsChange();
  }, [selectedCategories, categories]);

  useEffect(() => {
    if (categories.data?.length > 0) {
      categoriesMapper();
    }
  }, [categories]);

  if (categories.status == "pending") {
    return <Box />;
  }

  return (
    <MultipleSelect
      items={categories.data}
      selectedItems={selectedCategories}
      onPress={categoryChange}
    />
  );
};

const mapStateToProps = (state: any) => {
... 已移除此处无关逻辑
}

function mapDispatchToProps(dispatch: any) {
  let actions = bindActionCreators(
    {
      categoriesSaveAll,
    },
    dispatch
  );
  return { ...actions, dispatch };
}

export const CatsModal = connect(
  mapStateToProps,
  mapDispatchToProps
)(memo(CatsModalChild));

文章列表组件代码如下:

const Articles = (props) => {
  return (
    <ScrollView
      minH={600}
      zIndex={10}
      _dark={{
        bg: "gray.700",
      }}
      _light={{
        bg: "gray.100",
      }}
    >
      <Helmet>
        <meta charSet="utf-8" />
        <title>{props.title}</title>
        <link rel="canonical" href="http://pandaist.com/news" />
      </Helmet>
      <Center py={[25, 25, 25, 50, 50]}>
        <Heading
          fontSize={["4xl", "6xl", "6xl"]}
          _light={{
            color: "black",
          }}
          _dark={{
            color: "white",
          }}
        >
          {props.title}
        </Heading>
      </Center>
      <Filters hideCalendar={props.hideCalendar} />
      <RecentArticles {...props} title="Recent Articles" taxon={props.taxon} />
      <Box alignItems="center" justifyContent="center">
        <Categories taxon={props.taxon} />
      </Box>
      <CategoryArticles {...props} taxon={props.taxon} />
      <Box h={50} />
    </ScrollView>
  );
};

const mapStateToProps = (state) => {
  return {
    settings: state.settings,
    category: state.categories,
    resetdate: state.resetdate,
    date: state.date,
    readarticles: state.readarticles,
  };
};

function mapDispatchToProps(dispatch) {
  let actions = bindActionCreators({ settingsSave }, dispatch);
  return { ...actions, dispatch };
}

export default connect(mapStateToProps, mapDispatchToProps)(memo(Articles));

核心问题:
移除props.categoriesSaveAll(cats)后,按钮UI响应立刻变快;但调用这个方法时,按钮更新就会延迟。我试过用useEffect把Redux数据存到本地state来解耦,但没成功;设置延迟执行虽然能让按钮即时更新,但会卡住直到函数执行完成。

现在需要在基于全局存储初始化数据的前提下,成功把UI更新和全局存储的保存操作解耦。

编辑补充:以下是实际发起API查询的CategoryArticles组件代码:

const CategoryArticlesChild = (props) => {
  const [isLoading, setIsLoading] = useState(true);
  const [articleGroups, setArticleGroups] = useState([]);
  const [query, setQuery] = useState(false);
  const { data } = useCategoryArticlesAPIQuery(query ?? skipToken);

  const loadArticlesByCategory = async () => {
    const hsks = await getLevels();
    let _query = {
      category: props.settings.cats,
      limit: 15,
      ordering: "-date",
      published: 1,
      display_skill: hsks,
      taxon: props.taxon,
    };
    _query = processDates(props, _query);
    setQuery(_query);
  };

  const processMetaData = async (data) => {
    if (data) {
   ... 此处为数据处理逻辑,将数据复制到_data
      setArticleGroups(_data);
    }
  };

  useEffect(() => {
    processMetaData(data);
  }, [data]);

  useEffect(() => {
   loadArticlesByCategory();
  }, []);

  if (isLoading || articleGroups?.length === 0) {
    return (
      <Flex
        flexGrow={1}
        _dark={{
          bg: "gray.700",
        }}
        _light={{
          bg: "gray.100",
        }}
        alignSelf="center"
        justifyContent="center"
        w="100%"
        minH={500}
      >
        <Loading />
      </Flex>
    );
  }

  return (
    <FlatList
      zIndex={-1}
      data={articleGroups}
      renderItem={(articleGroup, index) => (
        <CardRow
          articleGroup={articleGroup.item}
          count={index}
          key={index}
          cardForm="WhiteCard"
        />
      )}
    />
  );
};

const CategoryArticles = memo(CategoryArticlesChild);

解决办法

  1. 异步更新Redux,不阻塞UI
    把Redux的更新操作放到异步队列里,让UI先更新,后台再处理全局存储的保存:

    const onCategoriesSelectionsChange = () => {
      if (categories.status === "fulfilled") {
        let selectedCats = selectedCategories.map((x: any) => x.value);
        let deleteCats = new Set(categories.data.map((x: any) => x.value));
        const difference = localCategories.filter((x) => !deleteCats.has(x));
        const cats = [...selectedCats, ...difference];
        // 用queueMicrotask异步执行,不卡UI
        queueMicrotask(() => {
          props.categoriesSaveAll(cats);
        });
      }
    };
    
  2. 修正本地state的同步逻辑
    原来的useEffect只在组件挂载时初始化localCategories,Redux数据更新时不会同步,把reduxCategories加入依赖数组:

    useEffect(() => {
      setLocalCategories([...reduxCategories]);
    }, [reduxCategories]);
    

    这样本地state能和Redux保持同步,同时按钮UI只依赖本地的selectedCategories,不会被Redux更新拖慢。

  3. 优化CategoryArticles的API调用逻辑
    当categories更新时,CategoryArticles会触发重渲染和新的API请求,这是卡顿的关键原因之一。用useMemo缓存查询参数,避免不必要的请求:

    const loadArticlesByCategory = async () => {
      const hsks = await getLevels();
      // 缓存查询参数,只有依赖变化时才重新生成
      const _query = useMemo(() => ({
        category: props.settings.cats,
        limit: 15,
        ordering: "-date",
        published: 1,
        display_skill: hsks,
        taxon: props.taxon,
      }), [props.settings.cats, props.taxon, hsks]);
      const processedQuery = processDates(props, _query);
      setQuery(processedQuery);
    };
    

    另外在processMetaData里添加setIsLoading(false),避免一直显示加载状态。

  4. 减少不必要的组件重渲染
    给useSelector加上shallowEqual,只有当categories数组内容真的变化时才更新组件:

    import { shallowEqual, useSelector } from 'react-redux';
    
    const reduxCategories = useSelector(
      (state: RootStateOrAny) => state.categories,
      shallowEqual
    );
    

内容的提问来源于stack exchange,提问作者Steven Matthews

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 09:55:19