React/Redux中最常用的将UI状态与全局存储状态解耦的方法



编辑:这似乎是由于在同一更新中调用api的一些问题,您使UI更新:

https://github.com/facebook/react-native/issues/32867

https://github.com/facebook/react-native/issues/35037

我有一个巨大的UI/UX问题——我有一组按钮,我想尽快更新样式,然后我还有一些其他组件,整个加载过程可能需要相当多的时间来完成。现在,按钮的更新时间和加载组件的更新时间一样长,这是非常糟糕的用户体验。

两个组件依赖于相同的数据-我的类别切片,看起来像这样:

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);
};
// This was an unsuccessful attempt to decouple my redux store from my local 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) => {
... irrelevant now, moved all state out of here
}
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}
//contentContainerStyle={{ justifyContent: "flex-start" }}
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));

我最大的问题是我的UI更新-当我删除:props.categoriesSaveAll(cats)一切都是即时的,UI是时髦的。我已经尝试将UI与通过useEffect钩子将数据放入本地状态解耦,但这仍然不能解决问题。

由于某种原因,无论何时我调用props.categoriesSaveAll(cats),无论我调用它,它都会导致这个问题。如果我在它触发之前设置一个等待时间,UI按钮会立即更新(但会冻结直到函数运行)。

如果我想让数据的初始填充基于全局存储,我如何才能成功地将UI更新与全局存储保存解耦?

编辑:我的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) {
... some data processing here, including copying data to _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);

看起来你把自己绑在绳结里了!

让我们把它清理干净。

一般来说,状态应该存在于一个地方,而且只能存在于一个地方。它看起来是在类slice reducer中。让我们删除localCategories和selectedCategories并派生selectedCategories。

通过删除所有不必要的useEffect钩子,我们应该能够大大减少额外渲染的数量。

更新:

让我们放回selectedCategories的本地状态,这样我们就可以"更新"了。组件ui,然后异步调度一个更新到redux。

为了简化selectedCategories状态的初始化,让我们将组件一分为二,其中顶层组件负责加载数据,然后将其传递给select组件以初始化状态。

export const CatsModal = (props: any) => {
/**
* Available categories from an api
*/
const { data, status } = useCategoriesAPIQuery({ taxon: props.taxon });
/**
* If the api query is pending, render a Box
*/
if (status === 'pending') {
return <Box />;
}
return <SelectCategory data={data} />;
};
const SelectCategory = ({ data }: any) => {
/**
* Now that react-redux hooks are standard, lets useDispatch, and useSelector, and drop the connect pattern
*/
const dispatch = useDispatch();
/**
* Selected Categories in redux state
*/
const categories = useSelector((state: RootStateOrAny) => state.categories);
/**
* A Category is selected if it is in the selected Categories redux state, and exists in the available Categories list
*/
const initialState = categories.filter(
(category) => data.find((item) => item.value === category) !== undefined
);
const [selectedCategories, setSelectedCategories] = useState(initialState);
/**
* Asyncronously update redux
*/
const saveAll = async (categories) => dispatch(categoriesSaveAll(categories));

/**
* On Change, if the item that was pressed is selected, then unselect it, else select it.
*/
const handlePress = (value: any) => {
let next: any = [];
if (selectedCategories.includes(value)) {
next = selectedCategories.filter((item) => item !== value);
} else {
next = [...selectedCategories, value];
}
setSelectedCategories(next);
saveAll(next);
};
return <MultipleSelect items={data} selectedItems={selectedCategories} onPress={handlePress} />;
};

相关内容

  • 没有找到相关文章

最新更新