成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

重學React:應用規?;疇顟B管理

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

圖片

前言

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

我們將學到如下核心知識點:

  • 如何選擇一個狀態管理庫
  • 如何在React中引入Redux
  • 如何編寫模塊化的Redux代碼
  • 如何通過RTK避免模版代碼
  • 如何編寫Redux中間件
  • 理解不可變數據的思想
  • 進一步優化拆分組件結構
  • ...

常用狀態庫選擇

現在react社區中的狀態管理庫有一打那么多,隨便說幾個:

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

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

想要大眾穩定一點,redux,mobx

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

之前案例問題分析

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

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

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

visibility,setVisibility明顯是TodoFilter內部狀態,現在因為過濾結果要傳給TodoList而不得不寫在外面:

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

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

圖片

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

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

在組件內部,也應該很容易取到想要的數據和方法,比如TodoList:

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

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

引入Redux

以前我使用redux需要安裝:redux + react-redux。痛點是概念多,代碼復雜冗長,心智負擔嚴重。

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

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

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

yarn add @reduxjs/toolkit react-redux

我想要快速嘗試一下,store/index.js:創建Store實例,用來存儲狀態

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
// configureStore()創建一個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`就是我們用來修改狀態的方法
reducers: {
increment: (state) => {
// Redux Toolkit 使我們可以直接修改狀態,大幅減少模版代碼
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
}
}
});
// 導出actionsCreator便于用戶使用,例如:dispatch(increment())
export const { increment, decrement } = counterSlice.actions;
// 導出子模塊reducer
export default counterSlice.reducer;

下面是在主文件中設置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>
)

這就準備好了,下面在組件中使用數據:

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

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

看看效果吧!

圖片

重構Todo應用

下面我們著手重構??TodoMVC???,首先將todos數據和操作移入獨立的??todoSlice.js??:

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

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

// 創建todoSlice保存todos狀態
// 將之前修改方法移至reducers中用來修改todos狀態
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用于選出想要的數據
export const selectTodos = (state) => state.todos.value;
// actionCreator用于創建dispatch()需要的action
export const { addTodo, removeTodo, updateTodo } = todoSlice.actions;
export default todoSlice.reducer;

現在我們不再需要在app.jsx中聲明todos,也不需要給TodoList傳遞數據:

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

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

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

我們再重構一下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狀態
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);
}
};
}

我們再提取新增組件感受一下變化,創建一個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)
// 修改新增待辦調用方式
dispatch(addTodo(newTodo));
setNewTodo("");
}
};
// ...
}

下面修改過濾組件FilterTodo的實現:這里需要提取visibility這個狀態到全局,因為修改組件是FilterTodo,關心它的卻是TodoList,創建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()方式設置visibility。

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

export default function TodoFilter() {
// 引入visibility
const visibility = useSelector(state state.visibility)
// 獲取選中狀態
const getSelectedClass = (filter) =>
visibility === filter ? "selected" : "";
// 引入dispatch
const dispatch = useDispatch();
// 設置過濾
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>
);
}

現在App.jsx中不在需要visibility狀態,也不需要傳遞他們給FilterTodo

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

下面我們看一下visibility發生變化之后,todoSlice中如何做出響應:我們添加一個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";
// 代碼真是相當舒適!
export const selectFilteredTodos = createSelector(
(state) => state.visibility, // 選出所需狀態作為輸入
(state) => state.todos.value,// 選出所需狀態作為輸入
(visibility, todos) => { // 接收輸入并執行派生邏輯
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;
}
}
);

全部搞定!現在再看看App.jsx,已經短小到不能再精悍了!

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變化觸發保存行為:這需要我們在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相關的action,我們都觸發保存行為
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)
})

后續更新計劃

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

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

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

責任編輯:武曉燕 來源: 村長學前端
相關推薦

2022-02-11 10:16:53

5G通信數字化轉型

2012-08-29 14:35:17

2020-12-22 16:10:43

人工智能

2025-01-08 07:02:00

人工智能GenAI美妝領域

2019-10-23 19:46:31

無人駕駛谷歌自動駕駛

2021-08-12 07:40:05

5G5G應用5G商業模式

2010-01-12 10:14:05

龍芯

2022-07-19 15:27:48

元宇宙區塊鏈貨幣

2022-07-04 14:28:31

5G4G數字經濟

2013-05-14 16:30:00

信息化中間件

2021-09-29 16:50:04

5G通信技術

2022-07-20 09:00:00

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

2021-12-29 14:57:47

德勤人工智能AI驅動型企業

2017-09-03 07:00:14

2021-08-09 21:02:02

云原生規?;?/a>演進

2013-01-24 13:22:58

用友UAP云平臺

2024-03-27 00:00:12

AI金融機構人工智能
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲第一福利视频 | 午夜影院在线观看免费 | 久久精品国产久精国产 | 国产精品免费在线 | 久久99精品久久久久久 | 天天欧美 | 操视频网站 | 亚洲成人播放器 | 久久国产日韩欧美 | 粉嫩粉嫩芽的虎白女18在线视频 | 亚洲热在线视频 | 欧美激情精品久久久久久免费 | 无码一区二区三区视频 | 亚洲国产成人精品久久久国产成人一区 | 日韩视频精品在线 | 99久久免费观看 | 极品久久 | 欧美日韩视频在线播放 | 中文字幕一区二区三区精彩视频 | 亚洲成人一区 | 国产精品久久久乱弄 | 日韩电影中文字幕 | 久久免费精品视频 | 精品综合| 九一视频在线观看 | 日本一区二区三区四区 | 国产精品99久久久久久大便 | www国产亚洲精品久久网站 | 日韩综合在线播放 | 精品国产91亚洲一区二区三区www | 欧美一区二区三区四区五区无卡码 | 黄色av免费网站 | 中文字幕乱码视频32 | 日本中出视频 | 91在线视频| 黄色免费三级 | 成人黄色三级毛片 | 91精品国产欧美一区二区 | 久久新视频 | 91精品久久久久久久99 | 一区二区三区视频免费观看 |