自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

重學(xué)React:應(yīng)用規(guī)?;疇顟B(tài)管理

開發(fā) 前端
我們通過TodoMVC?的例子掌握了React?的很多核心知識點,搞一個小應(yīng)用不成問題,但是,但凡上點規(guī)模的應(yīng)用都會需要狀態(tài)管理和路由?。所以,我們將繼續(xù)升級TodoMVC?,引入這兩個關(guān)鍵需求,使大家可以通過這個過程掌握規(guī)模化React應(yīng)用中如何用好狀態(tài)管理和路由功能。

圖片

前言

小羊們好!我們通過TodoMVC的例子掌握了React的很多核心知識點,搞一個小應(yīng)用不成問題,但是,但凡上點規(guī)模的應(yīng)用都會需要狀態(tài)管理和路由。所以,我們將繼續(xù)升級TodoMVC,引入這兩個關(guān)鍵需求,使大家可以通過這個過程掌握規(guī)?;疪eact應(yīng)用中如何用好狀態(tài)管理和路由功能。

我們將學(xué)到如下核心知識點:

  • 如何選擇一個狀態(tài)管理庫
  • 如何在React中引入Redux
  • 如何編寫模塊化的Redux代碼
  • 如何通過RTK避免模版代碼
  • 如何編寫Redux中間件
  • 理解不可變數(shù)據(jù)的思想
  • 進(jìn)一步優(yōu)化拆分組件結(jié)構(gòu)
  • ...

常用狀態(tài)庫選擇

現(xiàn)在react社區(qū)中的狀態(tài)管理庫有一打那么多,隨便說幾個:

  • redux、mobx、recoil、zustand、jotai、valtio、resso、...
  • 我說一下自己對它們的一些個人看法:
  • redux?:老牌勁旅,59k Star;使用復(fù)雜且臃腫,RTK的出現(xiàn)讓redux煥發(fā)了青春。
  • mobx:中堅力量,26k Star;使用簡單,響應(yīng)式,但是 "不夠 React"
  • recoil:貴族血統(tǒng),18k Star;Meta推出,使用簡單、原子化;但是處于試驗狀態(tài)
  • zustand:后起之秀,23.4k Star;小、快、靈,簡單版redux
  • jotai:10.7k Star;元數(shù)據(jù)化,hooks寫法,符合hooks理念
  • valtio:5.7k Star;proxy理念,"不太 React",用起來簡單
  • resso:0.3K Star;號稱世界上最簡潔的狀態(tài)管理庫,適用于RN、SSR、小程序

看完這些之后,大家很容易做選擇:

想要大眾穩(wěn)定一點,redux,mobx

  • 想要簡單,后續(xù)新出這幾個都屬于這種風(fēng)格
  • 想要時髦選recoil
  • 想要小巧選resso
  • 本次案例我將選用redux來做演示,它最具有代表性,設(shè)計理念也比較有學(xué)習(xí)價值。

之前案例問題分析

現(xiàn)在我們來看一下前面的案例中一些比較別扭的地方:

TodoList傳遞很多屬性和方法比較冗長:

<TodoList {...{todos: filteredTodos, removeTodo, updateTodo}}></TodoList>

visibility,setVisibility明顯是TodoFilter內(nèi)部狀態(tài),現(xiàn)在因為過濾結(jié)果要傳給TodoList而不得不寫在外面:

<TodoFilter visibility={visibility} setVisibility={setVisibility}></TodoFilter>

如果提取新增功能到獨立組件里也會遇到同樣的問題:操作的是todos,關(guān)心的卻是TodoList,很不好寫吧?

圖片

我們期待的??App.jsx??應(yīng)該是這樣的:簡單組合,組件之間又可以輕松共享數(shù)據(jù)和通信。

<AddTodo></AddTodo>
<TodoList></TodoList>
<TodoFilter></TodoFilter>

在組件內(nèi)部,也應(yīng)該很容易取到想要的數(shù)據(jù)和方法,比如TodoList:

function TodoList() {
// 一個hook可以輕松獲取數(shù)據(jù)
const todos = useSelector(state state.todos)
// 一個hook可以獲取修改數(shù)據(jù)的方法
const dispatch = useDispatch()
// 需要修改數(shù)據(jù)時派發(fā)action即可
dispatch(deleteTodoAction)
}

像上面這樣的需求,通過redux這樣的同一狀態(tài)管理庫就可以很容易實現(xiàn),下面我們來看一下具體做法。

引入Redux

以前我使用redux需要安裝:redux + react-redux。痛點是概念多,代碼復(fù)雜冗長,心智負(fù)擔(dān)嚴(yán)重。

現(xiàn)在官方推出了Redux Toolkit,以下簡稱RTK,我們可以使用RTK + react-redux 組合。

RTK主要用來簡化和優(yōu)化redux代碼。使用它可以輕松實現(xiàn)模塊化和可變數(shù)據(jù)寫法,簡直不要太好用,這也是我改變最初準(zhǔn)備使用mobx給大家演示的原因!

下面我們引入RTK 和 react-redux:

yarn add @reduxjs/toolkit react-redux

我想要快速嘗試一下,store/index.js:創(chuàng)建Store實例,用來存儲狀態(tài)

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
// configureStore()創(chuàng)建一個store實例
export const store = configureStore({
reducer: {
// counter即為模塊名稱
counter: counterReducer,
},
});

再看看counterSlice中的reducer定義部分:createSlice({...})定義子模塊,這樣可以很容易拆分代碼

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

// createSlice定義子模塊
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
// `reducers`就是我們用來修改狀態(tài)的方法
reducers: {
increment: (state) => {
// Redux Toolkit 使我們可以直接修改狀態(tài),大幅減少模版代碼
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
}
}
});
// 導(dǎo)出actionsCreator便于用戶使用,例如:dispatch(increment())
export const { increment, decrement } = counterSlice.actions;
// 導(dǎo)出子模塊reducer
export default counterSlice.reducer;

下面是在主文件中設(shè)置store,main.jsx:

import store from './store'
import { Provider } from 'react-redux'

ReactDOM.createRoot(document.getElementById('root')).render(
// Provider可以將store透傳下去
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>
)

這就準(zhǔn)備好了,下面在組件中使用數(shù)據(jù):

import { useSelector, useDispatch } from 'react-redux';
import { decrement, increment } from './store/counterSlice';

export function Counter() {
// useSelector(selector)獲取需要的子模塊數(shù)據(jù)
const count = useSelector(state state.counter.value);
// dispatch(action)用來修改狀態(tài)
const dispatch = useDispatch();
return (
<div>
<button notallow={() => dispatch(decrement())}>-</button>
<span>{count}</span>
<button notallow={() => dispatch(increment())}>+</button>
</div>
)
}

看看效果吧!

圖片

重構(gòu)Todo應(yīng)用

下面我們著手重構(gòu)??TodoMVC???,首先將todos數(shù)據(jù)和操作移入獨立的??todoSlice.js??:

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

const initialState = {
value: JSON.parse(localStorage.getItem("todomvc-react") || "[]"),
};

// 創(chuàng)建todoSlice保存todos狀態(tài)
// 將之前修改方法移至reducers中用來修改todos狀態(tài)
const todoSlice = createSlice({
name: "todos",
initialState,
reducers: {
addTodo: ({ value: todos }, { payload: title }) => {
const id = todos[todos.length - 1] ? todos[todos.length - 1].id + 1 : 1
todos.push({
id,
title,
completed: false,
});
},
removeTodo: ({ value: todos }, { payload: id }) => {
const idx = todos.findIndex((todo) => todo.id === id);
todos.splice(idx, 1);
},
updateTodo: ({ value: todos }, { payload: editTodo }) => {
const todo = todos.find((todo) => todo.id === editTodo.id);
Object.assign(todo, editTodo);
},
},
});

// selector用于選出想要的數(shù)據(jù)
export const selectTodos = (state) => state.todos.value;
// actionCreator用于創(chuàng)建dispatch()需要的action
export const { addTodo, removeTodo, updateTodo } = todoSlice.actions;
export default todoSlice.reducer;

現(xiàn)在我們不再需要在app.jsx中聲明todos,也不需要給TodoList傳遞數(shù)據(jù):

function App() {
// const {todos, addTodo, removeTodo, updateTodo} = useTodos(todoStorage.fetch())
return (
<div className="App">
{/* ... */}
<TodoList ></TodoList>
{/* ... */}
</div>
);
}

todos發(fā)生變化持久化到localStorage可以先注釋掉,隨后我們通過中間件的方式寫到todoSlice中

// useEffect(() => {
// todoStorage.save(todos);
// }, [todos]);

我們再重構(gòu)一下TodoList.jsx:

import { useDispatch, useSelector } from "react-redux";
import { selectTodos, removeTodo, updateTodo } from "./store/todos";

const TodoList = () {
// 獲取todos和dispatch
const todos = useSelector(selectTodos);
const dispatch = useDispatch();

const changeState = (e, currentTodo) => {
// currentTodo.completed = e.target.checked;
// 此處通過dispatch(updateTodo())方式通知更新todos狀態(tài)
dispatch(updateTodo({ ...currentTodo, completed: e.target.checked }));
};

// ...
const onEditing = (e) => {
const title = e.target.value;
if (title) {
setEditedTodo({ ...editedTodo, title });
} else {
// removeTodo(editedTodo.id);
// 使用dispatch(removeTodo())刪除指定項
dispatch(removeTodo(editedTodo.id));
}
};

const onEdited = (e) => {
if (e.code === "Enter") {
if (editedTodo.title) {
// updateTodo(editedTodo)
// 使用dispatch(updateTodo())更新
dispatch(updateTodo(editedTodo));
}
setEditedTodo(initial);
}
};
}

我們再提取新增組件感受一下變化,創(chuàng)建一個AddTodo.jsx:

import { useState } from "react";
import { useDispatch } from 'react-redux'
import { addTodo } from "./store/todoSlice";

export function AddTodo() {
// ...
// 這里只需要dispatch通知新增
const dispatch = useDispatch()
const onAddTodo = (e) => {
if (e.code === "Enter" && newTodo) {
// addTodo(newTodo)
// 修改新增待辦調(diào)用方式
dispatch(addTodo(newTodo));
setNewTodo("");
}
};
// ...
}

下面修改過濾組件FilterTodo的實現(xiàn):這里需要提取visibility這個狀態(tài)到全局,因為修改組件是FilterTodo,關(guān)心它的卻是TodoList,創(chuàng)建visibilitySlice.js:

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

export const VisibilityFilters = {
SHOW_ALL: "SHOW_ALL",
SHOW_COMPLETED: "SHOW_COMPLETED",
SHOW_ACTIVE: "SHOW_ACTIVE",
};

const visibilitySlice = createSlice({
name: "visibility",
initialState: VisibilityFilters.SHOW_ALL,
reducers: {
setVisibilityFilter(state, { payload }) {
return payload;
},
},
});

export const { setVisibilityFilter } = visibilitySlice.actions
export default visibilitySlice.reducer

注冊slice,store/index.js

import visibilitySlice from './visibilitySlice'

export default configureStore({
reducer: {
visibility: visibilitySlice
}
})

修改FilterTodo.jsx:可以看到FilterTodo通過dispatch()方式設(shè)置visibility。

import { useDispatch, useSelector } from "react-redux";
import {
VisibilityFilters,
setVisibilityFilter,
} from "./store/visibilitySlice";

export default function TodoFilter() {
// 引入visibility
const visibility = useSelector(state state.visibility)
// 獲取選中狀態(tài)
const getSelectedClass = (filter) =>
visibility === filter ? "selected" : "";
// 引入dispatch
const dispatch = useDispatch();
// 設(shè)置過濾
const setFilter = (filter) => dispatch(setVisibilityFilter(filter));

return (
<ul className="filters">
<li>
<button
className={getSelectedClass(VisibilityFilters.SHOW_ALL)}
notallow={() => setFilter(VisibilityFilters.SHOW_ALL)}
>
All
</button>
</li>
<li>
<button
className={getSelectedClass(VisibilityFilters.SHOW_ACTIVE)}
notallow={() => setFilter(VisibilityFilters.SHOW_ACTIVE)}
>
Active
</button>
</li>
<li>
<button
className={getSelectedClass(VisibilityFilters.SHOW_COMPLETED)}
notallow={() => setFilter(VisibilityFilters.SHOW_COMPLETED)}
>
Completed
</button>
</li>
</ul>
);
}

現(xiàn)在App.jsx中不在需要visibility狀態(tài),也不需要傳遞他們給FilterTodo

// const {visibility, setVisibility, filteredTodos} = useFilter(todos)
<TodoFilter></TodoFilter>

下面我們看一下visibility發(fā)生變化之后,todoSlice中如何做出響應(yīng):我們添加一個selectFilteredTodos選擇器

export const selectFilteredTodos = ({ visibility, todos }) => {
if (visibility === VisibilityFilters.SHOW_ALL) {
return todos.value;
} else if (visibility === VisibilityFilters.SHOW_ACTIVE) {
return todos.value.filter((todo) => todo.completed === false);
} else {
return todos.value.filter((todo) => todo.completed === true);
}
};

TodoList中只需要替換selectTodos為selectFilteredTodos即可:

const todos = useSelector(selectFilteredTodos);

大家看這像不像Vue中的計算屬性,或者Vuex中的getters,那既然要像,就要徹底一些,因此最好引入緩存性,即:todos和visibility不變化就沒有必要重新過濾。

這就需要用到RTK提供的createSelector方法,它的前身就是reselect,來看看具體用法:

import { createSelector } from "@reduxjs/toolkit";
// 代碼真是相當(dāng)舒適!
export const selectFilteredTodos = createSelector(
(state) => state.visibility, // 選出所需狀態(tài)作為輸入
(state) => state.todos.value,// 選出所需狀態(tài)作為輸入
(visibility, todos) => { // 接收輸入并執(zhí)行派生邏輯
switch (visibility) {
case VisibilityFilters.SHOW_ACTIVE:
return todos.filter((todo) => todo.completed === false);
case VisibilityFilters.SHOW_COMPLETED:
return todos.filter((todo) => todo.completed === true);
default:
return todos;
}
}
);

全部搞定!現(xiàn)在再看看App.jsx,已經(jīng)短小到不能再精悍了!

function App() {
return (
<div className="App">
<header>
<h1>我的待辦事項</h1>
<img src={reactLogo} className="logo" alt="logo" />
</header>
{/* 新增 */}
<AddTodo></AddTodo>
{/* 列表 */}
<TodoList></TodoList>
{/* 過濾 */}
<TodoFilter></TodoFilter>
</div>
);
}

export default App;

使用Redux中間件

還有最后一件事,就是todos信息變化之后要存入localStorage。

之前我們通過觀察todos變化觸發(fā)保存行為:這需要我們在App中額外引入todos,破壞了App短小精悍的感覺

import { useSelector } from 'react-redux'
function App() {
// 這需要我們在App中額外引入todos
const todos = useSelector(state state.todos)
useEffect(() {
storage.save(todos)
}, [todos])
}

實際上,我們可以利用redux中間件完成這個需求,store/index.js:

import {todoStorage} from '../utils/storage'

// 聲明一個中間件:只要是和todos相關(guān)的action,我們都觸發(fā)保存行為
const storageMiddleware = store next action {
if (action.type.startsWith('todos/')) {
next(action)
todoStorage.save(store.getState().todos.value)
}
}

const store = configureStore({
// 引入我們編寫的中間件
middleware: gDM gDM().concat(storageMiddleware)
})

后續(xù)更新計劃

終于寫完了!掌握了好多redux知識,但我們還是不滿足:

  • 我們能否將TodoMVC變成多頁面應(yīng)用
  • 能否引入權(quán)限控制,使得只有管理員才能創(chuàng)建和刪除待辦
  • 隨著Todo的逐漸復(fù)雜,看起來編輯Todo需要一個彈出表單
  • 等等...

這些功能我們會在后面的教程中帶大家逐步實現(xiàn),順便,我們再學(xué)一下路由庫的使用。

責(zé)任編輯:武曉燕 來源: 村長學(xué)前端
相關(guān)推薦

2022-02-11 10:16:53

5G通信數(shù)字化轉(zhuǎn)型

2012-08-29 14:35:17

2020-12-22 16:10:43

人工智能

2025-01-08 07:02:00

人工智能GenAI美妝領(lǐng)域

2010-01-12 10:14:05

龍芯

2021-08-12 07:40:05

5G5G應(yīng)用5G商業(yè)模式

2019-10-23 19:46:31

無人駕駛谷歌自動駕駛

2022-07-19 15:27:48

元宇宙區(qū)塊鏈貨幣

2022-07-04 14:28:31

5G4G數(shù)字經(jīng)濟(jì)

2013-05-14 16:30:00

信息化中間件

2021-09-29 16:50:04

5G通信技術(shù)

2022-07-20 09:00:00

管理項目規(guī)?;艚菘蚣?/a>科技

2021-12-29 14:57:47

德勤人工智能AI驅(qū)動型企業(yè)

2021-08-09 21:02:02

云原生規(guī)模化演進(jìn)

2017-09-03 07:00:14

2013-01-24 13:22:58

用友UAP云平臺

2024-03-27 00:00:12

AI金融機(jī)構(gòu)人工智能
點贊
收藏

51CTO技術(shù)棧公眾號