From 4043ad1f4095ce488fd929768a89d8111232ba51 Mon Sep 17 00:00:00 2001 From: geekact Date: Fri, 23 Feb 2024 21:52:43 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E4=B8=8Eredux-toolkit=E7=9A=84?= =?UTF-8?q?=E7=AE=80=E5=8D=95=E5=AF=B9=E6=AF=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/_sidebar.md | 4 +- docs/index.html | 28 ++- docs/redux-toolkit.md | 418 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 445 insertions(+), 5 deletions(-) create mode 100644 docs/redux-toolkit.md diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 175ac06..55a189a 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -18,4 +18,6 @@ - [编写测试](/test.md) -* [捐赠](/donate.md) +* [与Redux-Toolkit对比](/redux-toolkit.md) + +- [捐赠](/donate.md) diff --git a/docs/index.html b/docs/index.html index 5171591..8fd7873 100644 --- a/docs/index.html +++ b/docs/index.html @@ -38,10 +38,30 @@ vertical-align: text-top; } - @media screen and (max-width: 1000px) { - .markdown-section { - max-width: 93%; - } + .sidebar { + width: 260px; + } + + section.content { + left: 260px; + } + + .markdown-section { + max-width: 100%; + width: 100%; + box-sizing: border-box; + } + + .markdown-section tr { + background: transparent !important; + } + + .markdown-section table pre { + padding: 0 0.5em; + } + + .markdown-section table pre > code { + padding: 1.5em 0; } diff --git a/docs/redux-toolkit.md b/docs/redux-toolkit.md new file mode 100644 index 0000000..2f22eaa --- /dev/null +++ b/docs/redux-toolkit.md @@ -0,0 +1,418 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
toolkitfoca
开源时间2018-032021-10
文档地址 + +[foca.js.org](https://foca.js.org)(中文文档) + + + +[redux-toolkit.js.org](https://redux-toolkit.js.org/)(English documentation) + +
安装 + +```bash +# 持久化和计算属性需额外安装插件 +pnpm add @reduxjs/toolkit react-redux +``` + + + +```bash +pnpm add foca +``` + +
初始化 + +```typescript +// store.ts +import { useDispatch, useSelector } from 'react-redux'; +import { configureStore } from '@reduxjs/toolkit'; +import counterReducer from './counterSlice'; + +export const store = configureStore({ + reducer: { + // 项目中所有reducer都要import注册到这里(枯燥) + counter: counterReducer, + }, +}); + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; +export const useAppDispatch = useDispatch.withTypes(); +export const useAppSelector = useSelector.withTypes(); +``` + + + +```typescript +// store.ts +import { store } from 'foca'; + +foca.init({}); +``` + +
注入React + +```typescript +import { store } from './store'; +import { Provider } from 'react-redux'; + +ReactDOM.render( + + + , +); +``` + + + +```typescript +import './store'; +import { FocaProvider } from 'foca'; + +ReactDOM.render( + + + , +); +``` + +
创建Reducer + +```typescript +import { createSlice } from '@reduxjs/toolkit'; +import type { PayloadAction } from '@reduxjs/toolkit'; + +const initialState: { value: number } = { + value: 0, +}; + +export const counterSlice = createSlice({ + name: 'counter', + initialState, + reducers: { + increment: (state) => { + state.value += 1; + }, + decrement: (state) => { + state.value -= 1; + }, + incrementByAmount: (state, action: PayloadAction) => { + state.value += action.payload; + }, + }, +}); + +const { actions, reducer } = counterSlice; +export const { increment, decrement, incrementByAmount } = actions; +export default reducer; +``` + + + +```typescript +import { defineModel } from 'foca'; + +const initialState: { value: number } = { + value: 0, +}; + +export const counterModel = defineModel('counter', { + initialState, + reducers: { + increment(state) { + state.value += 1; + }, + decrement(state) { + state.value -= 1; + }, + incrementByAmount(state, amount: number /* ,arg2 ... */) { + state.value += amount; + }, + }, +}); +``` + +
组件中
获取数据
+ +```tsx +import { useAppSelector, useAppDispatch } from './store'; +import { increment } from './counterSlice'; + +export const Counter: FC = () => { + const count = useAppSelector((state) => state.counter.value); + const dispatch = useAppDispatch(); + + return
dispatch(increment())}>{count}
; +}; +``` + +
+ +```tsx +import { useModel } from 'foca'; +import { counterModel } from './counter.model'; + +export const Counter: FC = () => { + const count = useModel(counterModel, (state) => state.value); + + return
{count}
; +}; +``` + +
异步请求
和loading
+ +```typescript +import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; + +export const fetchTodosAsync = createAsyncThunk( + 'todos/fetchTodos', + async () => { + const response = await http.request('/api'); + return response.data; + }, +); + +const todoSlice = createSlice({ + name: 'todos', + initialState: { todos: [], status: 'idle' }, + reducers: {}, + extraReducers: (builder) => { + builder + .addCase(fetchTodosAsync.pending, (state) => { + state.status = 'loading'; + }) + .addCase(fetchTodosAsync.fulfilled, (state, action) => { + state.status = 'succeeded'; + state.todos = action.payload; + }) + .addCase(fetchTodosAsync.rejected, (state, action) => { + state.status = 'failed'; + }); + }, +}); + +export default todosSlice.reducer; +``` + + + +```typescript +import { defineModel } from 'foca'; + +export const todoModel = defineModel('todos', { + initialState: { todos: [] }, + reducers: {}, + methods: { + // 返回Promise时自带loading + async fetchTodos() { + const response = await http.request('/api'); + this.setState({ todos: response.data }); + }, + }, +}); +``` + +
组件中
使用loading
+ +```typescript +import { useAppSelector, useAppDispatch } from './store'; + +const Todo: FC = () => { + const dispatch = useAppDispatch(); + const loading = useAppSelector((s) => s.todos.status === 'loading'); + + useEffect(() => { + dispatch(fetchTodosAsync()); + }, [dispatch]); + + return null; +}; +``` + + + +```typescript +import { useLoading } from 'foca'; +import { todoModel } from './todo.model'; + +const Todo: FC = () => { + const loading = useLoading(todoModel.fetchTodos); + + useEffect(() => { + todoModel.fetchTodos(); + }, []); + + return null; +}; +``` + +
持久化 + +```bash +pnpm add redux-persist +``` + +```typescript +import { configureStore } from '@reduxjs/toolkit'; +import storage from 'redux-persist/lib/storage'; +import { combineReducers } from 'redux'; +import { persistReducer } from 'redux-persist'; +import counterReducer from './counterSlice'; + +const reducers = combineReducers({ + counter: counterReducer, +}); + +const persistConfig = { + key: 'root', + storage, + whitelist: ['counter'], +}; + +const persistedReducer = persistReducer(persistConfig, reducers); +const store = configureStore({ reducer: persistedReducer }); + +export default store; +``` + +```tsx +import { store } from './store'; +import { Provider } from 'react-redux'; +import { PersistGate } from 'redux-persist/integration/react'; +import { persistStore } from 'redux-persist'; + +ReactDOM.render( + + + + + , +); +``` + + + +```typescript +import { store } from 'foca'; +import { counterModel } from './counter.model'; + +foca.init({ + persist: [ + { + key: 'root', + storage: localStorage, + models: [counterModel], + }, + ], +}); +``` + +
计算属性 + +```bash +pnpm add reselect +``` + +```typescript +import { createSlice } from '@reduxjs/toolkit'; +import { createSelector } from 'reselect'; + +const todoSlice = createSlice({ + name: 'todos', + initialState: { todos: [] }, +}); + +const memoizedSelectCompletedTodos = createSelector( + [(state: RootState) => state.todos], + (todos) => { + return todos.filter((todo) => todo.completed === true); + }, +); + +// 在组件中使用 +const memoTodos = memoizedSelectCompletedTodos(state); +``` + + + +```typescript +import { defineModel } from 'foca'; + +export const todoModel = defineModel('todos', { + initialState: { todos: [] }, + computed: { + todos() { + return this.state.todos.filter((todo) => todo.completed === true); + }, + }, +}); + +// 在组件中使用 +const memoTodos = useComputed(todoModel.todos); +``` + +