redux
- Redux 是一个独立的 JavaScript 状态管理库
- https://www.redux.org.cn/
安装 Redux
npm i redux
yarn add redux
核心概念
理解 Redux 核心几个概念与它们之间的关系
- state 状态
- reducer 纯函数
- store 仓库
- dispatch 向reducer发起action
- getState 获取state
- subscribe 监听state
- action 动作
流程
- 首先通过createStore 创建仓库
- 基于render纯函reducer数初始化state
- 修改时通过store提供的dispatch方法发起一个action
- action被传入纯函数reducer中对state进行修改
Store 对象
为了对 state,Reducer,action 进行统一管理和维护,我们需要创建一个 Store 对象
import { createStore } from "redux";
function reducer(state = {}, action) {
return state;
}
const store = createStore(reducer);
console.dir(store);
dispatch
调用 dispatch 方法,可以向 store 发起 action,
store 会调用 reducer,将 action 传递给 reducer
是一个同步方法
getState
获取 store 中存储的 state
subscribe:
用于监听 state 变化,或取消监听,用于获取最新的state
import { createStore } from "redux";
function reducer(state = {
cnt: 1
}, action) {
console.log(action);
switch (action.type) {
case "add":
state.cnt++;
return state; // 把修改后的新的state返回
default:
break;
}
return state;
}
const store = createStore(reducer);
// console.dir(store);
store.subscribe(() => {
console.log("state修改了", store.getState());
});
setInterval(() => {
store.dispatch({
type: "add"
});
}, 1000);
state 对象
通常我们会把应用中的数据存储到一个对象树(Object Tree) 中进行统一管理,我们把这个对象树称为:state
state 是只读的
这里需要注意的是,为了保证数据状态的可维护和测试,不推荐直接修改 state 中的原数据
通过纯函数修改 state
什么是纯函数?
reducer 纯函数
- 相同的输入永远返回相同的输出
- 不修改函数的输入值
- 不依赖外部环境状态
- 无任何副作用
使用纯函数的好处
- 便于测试
- 有利重构
import { createStore } from "redux";
function reducer(state = {
cnt: 1
}, action) {
console.log(action);
switch (action.type) {
case "ADD":
state.cnt++;
return state; // 把修改后的新的state返回
case "MINUS":
state.cnt--;
return state;
}
return state;
}
const store = createStore(reducer);
// console.dir(store);
window.onload = function () {
let addBtn = document.createElement("button");
addBtn.innerHTML = "++";
addBtn.onclick = () => {
store.dispatch({
type: "ADD"
});
};
document.body.appendChild(addBtn);
let p = document.createElement("p");
render();
document.body.appendChild(p);
let minusBtn = document.createElement("button");
minusBtn.innerHTML = "--";
minusBtn.onclick = () => {
store.dispatch({
type: "MINUS"
});
};
document.body.appendChild(minusBtn);
store.subscribe(render);
function render() {
console.log("state修改了", store.getState());
p.innerHTML = store.getState().cnt;
}
};
action 对象
action 修改动作
- action 本质就是一个对象,
- 该对象必须有 type 属性,
- type 属性中,描述的是我们要对 state 做出哪种修改,
- reducer中会接收到 action ,然后完成对state的修改
我们对 state 的修改是通过 reducer 纯函数来进行的,同时通过传入的 action 来执行具体的操作
- type 属性 : 表示要进行操作的动作类型,增删改查……
- payload属性 : 操作 state 的同时传入的数据
但是这里需要注意的是,我们不直接去调用 Reducer 函数,而是通过 Store 对象提供的 dispatch 方法来调用
react-redux
react项目中的 redux 绑定库
<Provider store>
- connect() – 高阶函数:传入数据,返回一个函数
- useSelector 获取 state
- useDispatch 获取 dispatch
- useStore 获取 store
<Provider store=>
<Provider store>
使组件层级中的 connect() 方法都能够获得 Redux store
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from "react-redux";
import store from "./store/index";
import App from './app';
ReactDOM.render(
<Provider store={store}>
<App />,
</Provider>,
document.getElementById('root')
);
connect
let cb = connect((state) => {...state})
- 返回值state必须是个对象
- 通过connect可以让组件获取到state和dispatch
import React, { Component } from 'react';
import "./index.css";
import AddMessage from './addMessage';
import MessageList from './messageList';
import { connect } from 'react-redux';
function App(props) {
console.log(props);
let { data } = props;
return <section className="wrap">
<h2 className="title">留言板</h2>
<AddMessage />
{data.length > 0 ? <MessageList
data={data}
/> : ""}
</section>;
}
App = connect(state => state)(App);
export default App;
useSelector
用于获取state
useSelector(state => 我们需要的state相关数据
import React from 'react';
import "./index.css";
import AddMessage from './addMessage';
import MessageList from './messageList';
import { useSelector } from 'react-redux';
function App() {
let data = useSelector(state => state);
return <section className="wrap">
<h2 className="title">留言板</h2>
<AddMessage />
{data.length > 0 ? <MessageList /> : ""}
</section>;
}
export default App;
import React, { Component } from 'react';
import "./index.css";
import AddMessage from './addMessage';
import MessageList from './messageList';
import { useSelector, useDispatch, useStore } from 'react-redux';
function App() {
let data = useSelector(state => state);
let dispatch = useDispatch();
let store = useStore();
console.log(data, dispatch, store);
return <section className="wrap">
<h2 className="title">留言板</h2>
<AddMessage />
{data.length > 0 ? <MessageList
data={data}
/> : ""}
</section>;
}
export default App;
useDispatch 获取 dispatch
useStore 获取 store
实例
留言store
设置store创建仓库
设置action 方法
import { createStore } from "redux";
function reducer(state = [{
id: 0,
name: "昵称",
message: "留言内容留言内容留言内容留言内容留言内容1"
}, {
id: 1,
name: "昵称22",
message: "留言内容容留言内容留言内容留言内容留言内容留言内容33"
}], action) {
switch (action.type) {
case "ADD_MESSAGE":
return [{
id: Date.now(),
name: action.name,
message: action.message
}, ...state];
case "REMOVE_MESSAGE":
return state.filter(item => item.id !== action.id);
}
return state; //初始化
}
const store = createStore(reducer);
export default store;
组件获取store中的state,渲染数据
import React from "react";
import Message from "./message";
import { useSelector } from "react-redux";
export default function MessageList() {
let data = useSelector(state => state);
return <ul className="messageList">
{
data.map(item => <Message key={item.id} data={item} />)
}
</ul>;
}
组件dispatch提交方法
import React from "react";
import { useDispatch } from "react-redux";
export default function Message(props) {
let { data } = props;
let { id, name, message } = data;
let dispatch = useDispatch();
return <li>
<h3>{name}</h3>
<p>{message}</p>
<a onClick={() => {
dispatch({
type: "REMOVE_MESSAGE",
id
});
}}>删除</a>
</li>;
}
redux 三大原则
- 单一数据源: 整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中
- State 是只读的: 唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象
- 使用纯函数来执行修改
中间件
更新的过程中,去做一些其他的事情,
dispatch —> reducer 更新state
dispatch --> 中间件 --> reducer
异步中间件redux-thunk
因为dispatch是一个同步方法,想要一步获取数据的时候就需要异步操作
普通的异步操作
function List() {
let { type = "all" } = useParams();
let { loading, data } = useSelector(state => state);
let dispatch = useDispatch();
useEffect(() => {
dispatch({
type: "LOADING"
});
axios.get(`https://cnodejs.org/api/v1/topics?page=1&tab=${type}&limit=10`)
.then(res => {
dispatch({
type: "LOAD",
data: res.data.data
});
}, () => {
dispatch({
type: "LOAD",
data: []
});
});
}, [type]);
console.log(data);
return (<Fragment>
{loading ?
"数据获取中……"
:
(
data.length > 0 ? (
<ul className="list">
{data.map(item => (<li key={item.id}>{item.title}</li>))}
</ul>
) : "暂无数据"
)}
</Fragment>
);
}
redux-thunk
- 参数是对象,直接调用 reducer 修改我们的 state
- 参数是函数,调用该函数,并且把 dispatch 和 getState 传递我们的函数,可以在函数中,进行异步操作
启用thunk
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
function reducer(state = {
data: [],
loading: false
}, action) {
switch (action.type) {
case "LOADING": // 正在获取数据
return {
data: [],
loading: true
};
case "LOAD"://数据获取完成有数据
return {
data: action.data,
loading: false
};
}
return state;
}
//添加一个中间件
const store = createStore(reducer, applyMiddleware(thunk));
export default store;
可以把所有的action提取出去,减少组件内部逻辑
import React, { Fragment, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import { getListData } from "./store/action";
function List() {
let { type = "all" } = useParams();
let { loading, data } = useSelector(state => state);
let dispatch = useDispatch();
useEffect(() => {
dispatch(getListData(type));
}, [type]);
return (<Fragment>
{loading ?
"数据获取中……"
:
(
data.length > 0 ? (
<ul className="list">
{data.map(item => (<li key={item.id}>{item.title}</li>))}
</ul>
) : "暂无数据"
)}
</Fragment>
);
}
export default List;
抽离出来的dispatch方法
import axios from "axios";
function getListData(type) {
return (dispatch, getState) => {
dispatch({
type: "LOADING"
});
axios.get(`https://cnodejs.org/api/v1/topics?page=1&tab=${type}&limit=10`)
.then(res => {
dispatch({
type: "LOAD",
data: res.data.data
});
}, () => {
dispatch({
type: "LOAD",
data: []
});
});
};
}
export { getListData };
hooks替代thunk异步(hooks才是未来)
let getListData = useListData();
useEffect(() => {
getListData(type);
}, [type]);
useListData
import { useDispatch } from "react-redux";
import axios from "axios";
function useListData() {
let dispatch = useDispatch();
return (type) => {
dispatch({
type: "LOADING"
});
axios.get(`https://cnodejs.org/api/v1/topics?page=1&tab=${type}&limit=10`)
.then(res => {
dispatch({
type: "LOAD",
data: res.data.data
});
}, () => {
dispatch({
type: "LOAD",
data: []
});
});
};
}
export { useListData };
reducer的拆分
当state数据多的时候会比较乱
function index(state = {
list: {},
topic: {},
user: {}
}, action) {
return {
list: list(state.list, action),
topic: topic(state.topic = {}, action),
user: user(state.user = {}, action)
};
}
所以可以将每个reducer抽离出来,使得逻辑清晰
function index(state = {
list: {},
topic: {},
user: {}
}, action) {
return {
list: list(state.list, action),
topic: topic(state.topic = {}, action),
user: user(state.user = {}, action)
};
}
function list(list = {}, action) {
return list;
}
function topic(topic = {}, action) {
return topic;
}
function user(user = {}, action) {
return user;
}
export default index;