Redux是什么
Redux是状态管理层的解决方案,全局状态管理中心
我们看一个简单的小例子
import { useState } from "react";
function Counter(){
// State :计数器值
const [counter,setCounter] = useState(0)
// Action:当发生某一操作时,更新状态
const increment = ()=>{
setCounter(prevCounter=>prevCounter+1)
}
// View:UI定义
return(
<div>
Value:{counter}
<button onClick={increment}>Increment</button>
</div>
);
}
这是个简单的计数器案例
我们点击一次按钮,数值会加1
一个组件内部逻辑基本上可以分为这三个部分
Redux进行了一步抽象,将State与Action提取出来,让组件更专注的实现View
组件会从Redux中获取State更新状态,组件可以调用actions去修改Redux中State的值。这是Redux中基本的数据流的流程图
什么时候用Redux
B2想要和B3共享状态,我们可以将状态放在B1中
B2想要和A2共享状态,我们可以将状态放在A1中
这就叫做状态共享
但是如果时B2和A2这种,这就产生了很深层次的嵌套
Redux通过引入全局的状态管理的方式,任何一个组件,想要获取什么状态,直接从Redux中获取就行了,这就解决了状态一层一层传递的问题
如果组件内的状态管理逻辑比较复杂,需要分层的时候Redux也可以帮我们进行逻辑的管理
Redux术语
Store
store就是存储全局状态的一个地方
怎么去获取store对象呢?
我们可以通过configureStore这个api创建store对象
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({reducer:conterReducer})
console.log(store.getState())
我们可以通过@reduxjs/toolkit这个库引入configureStore
这个库其实就是基于reduxjs做的封装,简称RTK,通过这个库我们来使用Redux可以变得更加简洁
我们通过configureStore可以创建一个store对象,其中configureStore接收一个对象,这个对象有一个叫reducer的属性,待会我们会介绍
Action
Action描述状态发生的变化,他会对应状态修改的一个动作
Action其实就是一个普通的js对象
const addTodoAction = {
type:"todos/todoAdded",
payload:"Buy milk"
}
这个Action表明我要增加一个待办事项的这个条目,他所必须要包含的属性时type属性,type属性是一个字符串,这个字符串描述了Action对应的状态变化
Action Creator
Action Creator 是一个函数用来返回一个Action对象的
const add Todo = text =>{
return {
type:'todos/todoAdded',
payload:text
}
}
这是一个Action Creator,这个payload是通过函数传进来的参数。这可以让我们更灵活的创建Action。
Dispatch
Dispatch:发送一个Action
store.dispatch({type:'todos/todoAdded',payload:'learn react'})
console.log(store.gatState())
Reducer
Reducer是一个函数,会接收发送出来的Action,然后会根据这个Action和当前Redux的全局Store,进行一系列的处理,然后计算新的状态。
const initialState = { todos:[]}
function todoReducer(state=initialState,action){
if(action.type==='todos/todoAdded'){
return {
...state,
todos:[...state.todos,action.payload]
}
}
return state
}
逻辑处理流程
这个逻辑处理流程大概分为一下几步
1.事件产生
一个UI组件会定义一些事件,例如点击按钮等
2.发送Action
当事件产生了之后,想要修改State,但是不能够直接去修改,需要通过Dispatch发送一个Action给Reducer
3.Reducer处理逻辑
Reducer接收到Action之后,会根据Action的type执行对应的函数,根据Action的payload和当前的State更新State,
4.重新渲染UI组件
当State被更新后,会重新来渲染UI组件
Redux Toolkit
RTK是基于Redux的上层封装库,基于常见业务场景,提供了更加简洁的API,简化Redux的使用复杂度。
之前没有Redux Toolkit的时候我们需要写大量的模板代码,课程也是以Redux Toolkit为例来讲解如何使用Redux
Redux的使用我感觉还是优点乱的,这里以一个todolist来做一个例子吧
创建store
想要在全局使用Redux,我们需要在App组件的上面再包裹一个父组件Provider,这个组件是Redux提供的,需要接收store作为属性
那么store从何而来呢?
我们这里使用Redux Toolkit提供的configureStore() 生成。
先新建一个文件夹名叫states,用来存储全局state
在文件夹下创建一个store.js的文件,这个文件向外提供全局store
store.js
import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({
reducer: {
// Slice.reducer
}
})
export default store;
后面我们会创建Redux中存储的全局的state,创建之后需要将state的reducer放入reducer中,才能在全局使用。
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-Redux'
import store from './states/store'
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
创建slice
首先说一下slice是个什么东西,在前面我们提到了sotre中存放的是state与reducer,其中reducer中包含多个处理Action的程序,但是对于一个项目来说,我们通常不仅仅只有一个全局state和reducer要用Redux维护,所以Redux为store提供了slice,目的是为了能够将不同模块区分开来,同一模块的state和reducer包装在一个slice中,然后,再将slice的reducer传给store。
这里我也有一点不太明白,明明store中需要state与reducer,为什么在configureStore的时候,不是传入整个slice,而是单独传入reducer呢?问题先放下,我们就先记住,继续往下看,有明白的大佬,能否帮忙解答一下?
创建slice我们要利用createSlice这个api,这是'@reduxjs/toolkit'提供的api,接收一个name,一个initialState,作为初始的state,reducers,包含处理Action的各种方法
todoListSlice.js
import { createSlice } from '@reduxjs/toolkit'
const initialState = []
const todoListSlice = createSlice({
name: "todoList",
initialState,
reducers: {
addTodo(state, action) {
state.push(action.payload)
}
}
})
export const { addTodo } = todoListSlice.actions;
export default todoListSlice.reducer;
创建完成,我们需要向外界暴露出一个是addTodo的Action Creator,用来创建Action的,这个addTodo虽然和reducers中的函数名一样,但是并不是同一个意思,我第一次在这里也糊涂了。
先说一下createSlice返回的对象
这里面的Actions中的addTodo其实是一个actionCreator,用来产生Action的
而我们定义在reducers中的addTodo方法才是真正的reducer,用来执行state的更新的方法。
既然我们已经创建了slice了,那么我们也需要将slice的reducer放进store的reducer才行,这样才能在全局使用这个slice
更新store.js
import { configureStore } from '@reduxjs/toolkit';
import todoList from './todoListSlice';
const store = configureStore({
reducer: {
todoList
}
})
export default store;
使用state
到目前为止,我们相当于已经完成了store部分的工作
下面就是怎么使用state,想要使用state,我们需要用到react-redux提供的useSelector() 这个hook方法。
使用示例
App.js
import './App.css';
import { useState } from 'react';
import { useSelector } from 'react-redux';
function App() {
const [todoText, setTodoText] = useState("")
const todoList = useSelector(state => state.todoList)
const onInputChange = (event) => {
}
const onAddButtonClick = (event) => {
}
return (
<div className="App">
<div>
<input type="text" onChange={onInputChange} />
<button onClick={onAddButtonClick}>添加</button>
</div>
<div>
{
todoList.map(todoItem => {
return (
<div key={todoItem.id}>
{todoItem.text}
</div>
)
})
}
</div>
</div>
);
}
export default App;
如果我们将state的初始值设置为
const initialState = [
{
id:0,
text:"Hello"
},
{
id:1,
text:"world"
}
]
那么页面就会显示这样的效果了
更新state
上面我们已经实现了在组件中使用state
那我们怎么实现更新state呢?
更新state需要在EventHandle中调用Dispatch,Dispatch需要通过react-redux提供的 useDispatch 这个hook方法获取,
Dispatch会发送一个Action,这个Action需要我们使用ActionCreator创建,就是刚刚我们在slice中export的ActionCreator方法
最终实现代码:
import './App.css';
import { useState } from 'react';
import { useDispatch,useSelector } from 'react-Redux';
import { nanoid } from '@reduxjs/toolkit';
import { addTodo } from './states/todoListSlice'
function App() {
const [todoText, setTodoText] = useState("")
const todoList = useSelector(state => state.todoList)
const dispatch = useDispatch();
const onInputChange = (event) => {
setTodoText(event.target.value)
}
const onAddButtonClick = (event) => {
// 提交
dispatch(
addTodo({
text: todoText,
id: nanoid()
}),
);
}
return (
<div className="App">
<div>
<input type="text" onChange={onInputChange} />
<button onClick={onAddButtonClick}>添加</button>
</div>
<div>
{
todoList.map(todoItem => {
return (
<div key={todoItem.id}>
{todoItem.text}
</div>
)
})
}
</div>
</div>
);
}
export default App;
最终实现效果:
到目前为止,就已经把Redux加Redux Toolkit的使用流程讲完了。
异步数据流
但是实际项目中我们会遇到的问题是,当我们点击动作后,需要向服务端发起数据获取请求,当数据请求到了之后,再进行reducer中修改数据,那这样我们怎么处理呢?
这就是Redux的异步数据流
在Redux toolkit中为我们提供了createAsyncThunk() 的api,这个相当于一个中间件。
接收到Event Handler的Dispatch之后,并不会立即将Dispatch发送传给reducer,而是被中间件拦截,中间件会再向Reducer发送Action.
对于createAsyncThunk()这个api在接收到Action时会给reducer发送一个pending的Action,异步结果返回时如果有错误,会发送一个rejected的Action,如果成功,会发送一个fulfilled的Action
具体的用法可以参照这个例子。
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { client } from '../api/client';
const initialState = {
posts: [],
status: 'idle',
error: null,
};
export const fetchPosts = createAsyncThunk('posts/fetchPosts', async () => {
const response = await client.get('/fakeApi/posts');
return response.posts;
});
export const addPost = createAsyncThunk(
'posts/addPost',
async (initialPost) => {
const response = await client.post('/fakeApi/posts', { post: initialPost });
return response.post;
},
);
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {
// addPost(state, action) {
// state.posts.push(action.payload);
// },
},
extraReducers: {
[fetchPosts.pending]: (state, action) => {
state.status = 'loading';
},
[fetchPosts.fulfilled]: (state, action) => {
state.status = 'succeeded';
state.posts = action.payload;
},
[fetchPosts.rejected]: (state, action) => {
state.status = 'failed';
state.error = action.error.message;
},
[addPost.fulfilled]: (state, action) => {
state.status = 'idle'
},
},
});
// export const { addPost } = postsSlice.actions;
export default postsSlice.reducer;
日记
我真的想哭了,这Redux是什么东西,太乱了吧,迟早完蛋。(流下了不学无术的泪水)
边学习,边做笔记的我一度想要放弃。
我就想要创建一个全局变量,有这么难?
我觉得Redux的这种处理逻辑没有问题,但是编写代码的时候,感觉代码的流程或者逻辑很混乱
React Redux的学习我已经脱了两天了,前两天是因为有事,但昨天真的是没学明白。从框图的逻辑上来说,我可以理解,但是代码流就感觉没什么逻辑。