- 什么是状态管理
- Redux 的核心思想
- React-redux 介绍
什么是状态管理
所谓状态管理,指的是把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测。
组件之间通常会有一些共享的状态,在 Vue 或者 React 中我们一般会将这部分状态提升至公共父组件的 props 中,由父组件来统一管理共享的状态,状态的改变也是由父组件执行并向下传递。这样会导致两个问题:
- 需要将共享的状态提升至公共的父组件,若无公共的父组件,往往需要自行构造
- 状态由父组件自上而下逐层传递,若组件层级过多,数据传递会变得很冗杂
此时,我们就需要一个统一的仓库来对组件状态进行管理,如下图:
Redux 的核心思想
早期的时候,React 官方提供了 Flux ,Flux 的特点如下:
- 单向数据流。视图事件或者外部测试用例发出 Action ,经由 Dispatcher 派发给 Store ,Store 会触发相应的方法更新数据、更新视图
- Store 可以有多个
- Store 不仅存放数据,还封装了处理数据的方法
2015 年的时候,Dan Abramov 推出的 Redux 席卷了整个 React 社区,Redux 本质就是在 Flux 上做了一些更新:
- 单向数据流。View 发出 Action (
store.dispatch(action)
),Store 调用 Reducer 计算出新的 state ,若 state 产生变化,则调用监听函数重新渲染 View (store.subscribe(render)
) - 单一数据源,只有一个 Store
- state 是只读的,每次状态更新之后只能返回一个新的 state
- 没有 Dispatcher ,而是在 Store 中集成了 dispatch 方法,store.dispatch() 是 View 发出 Action 的唯一途径
- 支持使用中间件(Middleware )管理异步数据流
Redux 官网:https://redux.js.org/
Redux 数据流:
Redux 示例:ToDoList
在浏览器中使用 redux 扩展工具,首先需要打开谷歌浏览器的扩展应用商店,下载 redux devtools
接下来需要在创建 store 的地方,添加如下的代码:
export const store = createStore(
todoReducer,
+ window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
详细可以参阅:https://github.com/zalmoxisus/redux-devtools-extension#usage
React-redux 介绍
Redux 是一个独立的第三方库,之后 React 官方在 Redux 的基础上推出了 _React-redux_:https://react-redux.js.org/
最新版的 React-redux ,已经全面拥抱了 Hooks ,内置了诸如:
- useSelector
- useDispatch
- useStore
一类的 Hook ,我们只要掌握这一类 Hook ,就可以轻松上手。
另外,Redux 官方还推出了 Redux Toolkit ,来简化整个 Redux 的使用。官方文档:https://redux-toolkit.js.org/
因此现在在 React 应用中,状态管理库的使用一般都是 React-redux + Redux Toolkit
React-redux 示例:ToDoList
和后端进行交互
一般来讲,当数据发生变化时,不仅仅是前端的状态库要更新数据,服务器端也要对应的对数据进行更新,此时的更新流程如下:
和后端交互示例:学生管理系统
基本使用流程:
1、安装redux
npm i redux
2、创建一个redux文件夹
3、store.js:引入仓库,引用reducers,启用仓库
// 创建仓库
import { createStore } from "redux";
// 引入 reducer
import { todoReducer } from "./reducers"
// 需要你传入一个 reducer(纯函数,用于计算最新的状态)
export const store = createStore(
todoReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
4、引入的reducers.js里面定义了仓库的状态也就是数据,以及处理方法todoReducer,更新状态,todoReducer是纯函数
// reducer,用于计算最新的状态
import { ADD,DEL,CHANGE } from "./actionType";
// 仓库一开始默认的数据
let defaultState = {
list: [
{
content: "学习 React",
status: false,
},
{
content: "复习 Vue",
status: false,
},
{
content: "玩游戏",
status: false,
},
{
content: "听歌",
status: false,
},
],
};
/**
* 通过这个纯函数我们会计算出最新的状态
* state : 仓库数据,每次会传入上一次的仓库数据
* action : 描述对象 {type : ADD, data : "学习 redux"}
* 描述对象描述了我要做什么,以及带来的额外数据
*/
export function todoReducer(state = defaultState, action) {
// 有了描述对象后,我就可以根据旧状态计算出新的状态并返回
switch (action.type) {
case ADD: {
// 新增的操作
const arr = [...state.list];
arr.push({
content: action.data,
status: false,
});
return { list: arr };
}
case DEL : {
const arr = [...state.list];
arr.splice(action.data, 1);
return { list: arr };
}
case CHANGE : {
const arr = [...state.list];
arr[action.data].status = !arr[action.data].status;
return { list: arr };
}
default: return state;
}
}
5、actionType定义了描述action对象的类型,这个不重要
// 描述了 action 对象的类型
export const ADD = "ADD"; // 新增
export const DEL = "DEL"; // 删除
export const CHANGE = "CHANGE"; // 修改状态
6、action.js定义的是每一个action对象,每一个action的返回值,就是作为我们在todoReducer函数调用时的第二个参数
// 生产 action 对象的函数,我们一般称之为 actionCreator
import { ADD, DEL, CHANGE } from "./actionType";
export const addListAction = newItem => ({
type: ADD,
data : newItem
})
export const delListAction = index => ({
type: DEL,
data : index
})
export const changeAction = index => ({
type: CHANGE,
data : index
})
7、在index.js中引入并挂在,注意store.subscribe(render)这句话,所谓的订阅就是当仓库发生改变时渲染整个页面(个人觉得这里有渲染问题,性能不高)
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
// 引入仓库
import { store } from "./redux/store";
const root = ReactDOM.createRoot(document.getElementById('root'));
function render() {
root.render(
<App store={store} />
);
}
render();
// 让 redux 和 react 建立关联
// subscribe 叫做订阅,在仓库的状态发生改变的时候
store.subscribe(render);
8、作为props传入到app.js,同时传入到子组件
import Input from "./components/Input";
import List from "./components/List";
import "./css/App.css"
function App(props) {
return (
// 最外层容器
<div className="container">
<h1 className="lead" style={{
marginBottom : "30px"
}}>待办事项</h1>
<Input store={props.store}/>
<List store={props.store}/>
</div>
)
}
export default App;
9、在input.jsx中使用,引入action.js中定义的action,然后通过props.store.dispatch触发,触发后会被派发到reducer里面,执行todoReducer方法,从而更新state的状态
import { useState } from "react";
import { addListAction } from "../redux/actions";
function Input(props) {
const [value, setValue] = useState("");
function clickHandle() {
// 将用户填写的内容提交到仓库
// dispatch 方法会派发一个 action 对象到 reducer 里面
// addListAction(value) ===> { type : ADD, data : value}
// 这个就是我们的 action 描述对象,该对象会被 dispatch(派发)到 reducer 里面
props.store.dispatch(addListAction(value));
setValue("");
}
return (
<div className="form-inline">
<input
type="text"
className="form-control"
placeholder="请输入待办事项"
style={{
marginRight: 10,
}}
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<button className="btn btn-primary" onClick={clickHandle}>
提交
</button>
</div>
);
}
export default Input;
10、list.jsx一样的东西,需要注意的是,在 redux,通过 store.getState() 来获取仓库数据
import React from "react";
import { delListAction, changeAction } from "../redux/actions";
function List(props) {
// 在 redux,通过 store.getState() 来获取仓库数据
const lis = props.store.getState().list.map((item, index) => {
return (
<li key={index} className="text-primary">
<span
onClick={() => props.store.dispatch(changeAction(index))}
className={["item", item.status ? "completed" : ""].join(" ")}
>
{item.content}
</span>
<button
type="button"
className="close"
onClick={() => props.store.dispatch(delListAction(index))}
>
×
</button>
</li>
);
});
return (
<div>
<ul style={{ marginTop: 20 }}>{lis}</ul>
</div>
);
}
export default List;