跳到主要内容

数据流方案

recoil

// GlobalStore
import { atom } from 'recoil';

export interface GlobalStateType {
userID: string;
userName: string;
year?: number;
}

export const GlobalState = atom<GlobalStateType>({
key: 'GlobalState',
default: {
userID: '001',
userName: 'JJC',
},
});

// pageStore
import { selector } from 'recoil';
import { GlobalState } from '@/store/index.ts';
import axios from '@/common/utils/request';
import { message } from '@byte-design/ui';

export const templateListDataState = selector({
key: 'templateListDataState',
get: async ({ get }) => {
const { userID } = get(GlobalState);
try {
const { data } = await axios.post('template_list', {
params: {
userID,
},
});
return data.list;
} catch (error) {
message.error('系统错误');
return [];
}
},
});

// page
const templateListData = useRecoilValue<TemplateListItem[]>(templateListDataState);

Redux + TS + hooks

Store

import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import reducer from './reducer';
import thunk from 'redux-thunk';

let middleware = applyMiddleware(thunk);

if (process.env.NODE_ENV !== 'production') {
middleware = composeWithDevTools(middleware);
}

const store = createStore(reducer, middleware);

export default store;

Reducer

import { combineReducers } from 'redux';
import { templateReducer } from './TemplateStore/reducer';
import { landingPageReducer } from './LandingPageStore/reducer';
import * as Types from './types';

export interface RootStateType {
template: Types.TemplateTypes.IInitialState;
landingPage: Types.LandingPageTypes.IInitialState;
}

export default combineReducers<RootStateType>({
template: templateReducer,
landingPage: landingPageReducer,
});

Type

export * as TemplateTypes from './TemplateStore/types';
export * as LandingPageTypes from './LandingPageStore/types';

LandingPageStore

type

export enum ActionTypes {
SET_LANDING_PAGE_LIST = 'SET_LANDING_PAGE_LIST',
}

export interface IInitialState {
list: ILandingPageItem[];
}

export interface ILandingPageItem {
id: string;
name: string;
tag: string;
ctr: number;
cvr: number;
img: string;
}

interface SetLandingPageListAction {
type: keyof typeof ActionTypes;
payload: ILandingPageItem[];
}

export type LandingPageActions = SetLandingPageListAction;

actions

import * as Types from './types';
import axios from 'axios';

export const getLandingPageList = () => async (dispatch: any) => {
try {
const { data } = await axios.post('site_list');
dispatch(setLandingPageList(data.data.list));
} catch (error) {
console.log(error);
}
};

export function setLandingPageList(
templateList: Types.ILandingPageItem[],
): Types.LandingPageActions {
return {
type: Types.ActionTypes.SET_LANDING_PAGE_LIST,
payload: templateList,
};
}

reducer

import * as Types from './types';

const initialState: Types.IInitialState = {
list: [],
};

export function landingPageReducer(
state = initialState,
action: Types.LandingPageActions,
): Types.IInitialState {
switch (action.type) {
case Types.ActionTypes.SET_LANDING_PAGE_LIST:
return {
...state,
list: action.payload as Types.ILandingPageItem[],
};
default:
return state;
}
}

APP

import { useSelector, useDispatch } from 'react-redux';
import { getLandingPageList } from '@/store/LandingPageStore/actions';
import * as Types from '@/store/LandingPageStore/types';
import { RootStateType } from '@/store/reducer';

const dispatch = useDispatch();
const { list } = useSelector((state: RootStateType) => state.landingPage);
useEffect(() => {
dispatch(getLandingPageList());
}, [dispatch]);

Reselect:createSelector

createSelector(...inputSelectors | [inputSelectors], resultFunc) 这个 API 接收选择器(input-selectors)和变换函数作为参数,选择器返回的值会被作为变换函数的入参,你可以在这里进行更细的筛选。 如果input-selectors的值不变,即变换函数的入参不变,说明最后的变换结果也不会变,那么reselect会直接返回缓存起来的值

import { createSelector } from "reselect";



const selectorA = (state) => state.account.username;
const selectorB = (state) => state.account.info;

const selectSomeData = createSelector(
// 不一定要数组哈,也可以分开传
[selectorA, selectorB],
(username, info) => ({ ...username, infoDeatil: info[username.info] })
);

// 一个copy的例子
const getVisibilityFilter = (state) => state.visibilityFilter;
const getTodos = (state) => state.todos;

export const getVisibleTodos = createSelector(
[getVisibilityFilter, getTodos],
(visibilityFilter, todos) => {
switch (visibilityFilter) {
case "SHOW_ALL":
return todos;
case "SHOW_COMPLETED":
return todos.filter((t) => t.completed);
case "SHOW_ACTIVE":
return todos.filter((t) => !t.completed);
}
}
);

useSelector

它同样也会订阅 store,并且在你每分发一个 action 就会执行一次。

你可以在一个函数组件中多次调用 useSelector()。每一个 useSelector() 的调用都会对 Redux 的 store 创建的一个独立的 订阅(subscription)。由于 Redux v7 的 批量更新(update batching) 行为,对于一个组件来说,如果一个 分发后(dispatched) 的 action 导致组件内部的多个 useSelector() 产生了新值,那么仅仅会触发一次重渲染。

当你分发 action 后,它会将上一次调用的结果和本次调用的结果进行比较(通过严格比较===,connect 使用的是浅比较),如果不一样,组件才会被强制重渲染。

严格比较 === 对应的是 疏松比较 ==,深比较(深比较会递归进行浅比较)对应的是浅比较。 因为这个可能在任何时候执行多次,所以你要保持这个selector是一个纯函数。 useSelector()默认使用===(严格相等)进行相等性检查,而不是浅相等(==)。 默认情况下是,如果每次返回一个新对象将始终进行强制re-render.

浅比较更新

import { shallowEqual, useSelector } from 'react-redux'

// later
const selectedData = useSelector(selectorReturningObject, shallowEqual)

结合Reselect使用记忆化(memoizing) selector

todos依赖前面的state

import { createSelector } from "reselect";
import { useSelector } from "react-redux";

const selectUserDisplay = createSelector(
(state) => state.currentUser,
(state) => state.entities.jobs,
(user, jobs) => ({ ...user, job: jobs[user.job] })
);

// 在组件里
const user = useSelector(selectUserDisplay);

如果依赖props怎么办,只能放到组件里去取,在组件里createSelector又会重复执行。只能用useCallback去包裹

import { useCallback } from "react";
import { createSelector } from "reselect";
import { useSelector } from "react-redux";

// 下面全部在组件里
const { id } = props;
const selectUserDisplay = useCallback(
createSelector(
(state) => state.users,
(state) => state.entities.jobs,
(users, jobs) => {
const { job, ...user } = users[id];
return { ...user, job: jobs[job] };
}
),
[id]
);
const user = useSelector(selectUserDisplay);

不同于普通的纯函数,createSelector 是有开销的,包括组装函数的时间开销,以及开辟一个内部缓存的空间开销。useCallback 虽然能稳定返回的函数,但并不减少 createSelector 的调用次数(这里是执行createSelector后返回一个函数给useCallback作为参数),只是一部分调用所返回的结果被直接丢弃,等着 GC 回收。但是,GC 是性能的大敌,从 Immutable 到 useCallback 产生的碎片,这是整个 React 当前的性能模型所未能解决的问题。

最优解,是放到useMemo里。这种思路使得细粒度筛选 store 和良好缓存能力很好的共存了,而且也能使用组件内部的状态/属性来参与筛选。

import { useMemo } from "react";
import { useSelector } from "react-redux";

// 组件里
const { id } = props;
const users = useSelector((s) => s.entities.users);
const jobs = useSelector((s) => s.entities.jobs);
const userDisplay = useMemo(() => {
const { job, ...user } = users[id];
return { ...user, job: jobs[job] };
}, [id, users, jobs])

@reduxjs/toolkit 真香