最易懂-react18入门系列四

84 阅读6分钟

摘要

本篇主要是redux讲解。

  • redux

redux

Redux 是React里最常用的集中状态管理工具,类似vue里的vuex,是可以独立于框架之外运行的。

redux一般存的是全局通用的数据,这样,很多组件需要用到的数据存到这里,数据线就会非常清晰而且简单。

使用步骤

  1. 定义reducer函数

  2. 使用createStore方法传入reducer函数,生成一个store实例

  3. 通过store实例subcribe订阅数据变化(数据一旦变化,就可以得到通知)

  4. 通过store实例的dispatch函数提交action更新状态(告诉reducer 你想怎么改数据)

  5. 通过store实例的getState方法获取最新状态更新到视图中

步骤多,很多是一个样板代码。

redux快速体验

既然可以独立于react运行,那么我们单独使用下。不和任何框架绑定,不使用任何构建工具。使用纯redux实现计数器。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <button id="dec">-</button>
    <button id="count">0</button>
    <button id="add">+</button>


    <script src="https://unpkg.com/browse/redux@5.0.0-alpha.0/dist/redux.js"></script>
    <script>
        // 1.定义reducer函数,根据不同的action对象 返回不同的新的state
        // 两个参数,一个是state 管理的数据;另一个是action,其属性type标记什么情况下想要做什么样的修改
        function reducer(state={count:0},action){
            if(action.type == 'add'){
                return {count:state.count++}//数据不可变原则,跟useState一样,不能修改,只能覆盖。

            }else if(action.type == 'dec'){
                return {count:state.count--}
            }
            return state
        }

        // 2. 传入reducer函数生成一个store实例
        const store = Redux.createStore(reducer)

        // 3. 通过store实例的subscribe方法订阅数据的变化,每次state变化会自动进入回调执行
        store.subscribe(()=>{
            console.log('state发生变更,值为',store.getState())
            //5. 通过store.getState方法获取最新值并写入到视图
            document.getElementById('count').innerText = store.getState().count
        })

        // 4. 通过store实例的dispatch函数提交action更改状态
        const addBtn = document.querySelector('#add')
        addBtn.addEventListener('click',()=>{
            //++
            store.dispatch({
                type:'add'
            })
        })
        const decBtn = document.querySelector('#dec')
        decBtn.addEventListener('click',()=>{
            //--
            store.dispatch({
                type:'dec'
            })
        })
    </script>
</body>
</html>

redux管理数据流程梳理:

为了职责清晰,数据流向明确,Redux把整个数据修改的流程分成了三个核心概念,分别是:state/action/reducer

  1. state: 一个对象,存放着我们管理的数据状态

  2. action: 一个对象, 用来描述你想怎么改数据

  3. reducer: 一个函数,根据action的描述生成一个新的state

redux 和 react 结合

redux投入到react项目中是最常用的。

在React里使用redux,官方要求额外要安装另外2个npm插件:

  1. Redux Toolkit(RTK),官方推荐编写Redux逻辑的方式,是一套工具集合。可以简化redux的写法。

  2. react-redux:用来连接 redux 和 组件的 中间件。

一.通过cli创建React项目环境

创建名字:test-redux 的项目

npx create-react-app test-redux

二.安装redux配套插件

npm i @reduxjs/toolkit react-redux

启动项目。

三.创建store目录

│├── store                     状态机配置
││├── index.js                 状态机入口
││└── modules                  状态机模块
│ │    └── counterStore.js

我们依然实现一个计数器

整体路径熟悉

  1. redux store里的相关逻辑:
  • 配置子模块

  • 配置store并注册子模块

  1. react组件里的相关逻辑:
  • 注入store[使用 react-redux做桥接]

  • 使用store中的数据

  • 修改store中数据

四.子模块书写

store/modules/counterStore.js

使用react toolkit 创建子store模块。

import { createSlice } form '@reduxjs/toolkit'

const counterStore = createSlice({
	name:'counter',

	//初始化state
	initialState:{
		count:0
	},

	//修改状态的方法, 同步方法, 支持直接修改
	reducer:{
		add(state){
			state.count ++
		},
		dec(state){
			state.count --
		}
	}
})

// 解构出来actionCreater 函数
const {add,dec} = counterStore.actions

// 获取reducer
const reducer = counterStore.reducer

// 以按需导出的方式导出actionCreater
export {add,dec}

// 以默认的方式导出reducer
export default reducer


五.状态机入口引入注册书写

store/index.js

import { configureStore} from '@reduxjs/toolkit'

import counterStore from './modules/counterStore'

// 创建根store并组合子模块
const store = configureStore({
	reducer:{
		// counter这个名字就是后面页面里useSelector回调里使用的store子模块
		counter:counterStore
	}
})

export default store

六.app主入口引入store

主要利用react-redux 的 内置 Provider 组件 来做store的全局注入。

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

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
	<Provider store={store}>
		<App/>
	</Provider>
)

七. react组件里使用store数据

主要利用react-redux 的一个钩子函数useSelector。它的作用是把store中数组映射到组件中去。

app.jsx

import {useSelector} from 'react-redux'

function App(){

	const { count } = useSelector(state => state.counter)//useSelector 里是一个回调函数的形式,return回store

	return(
		<div className="App">
			{count}
		</div>
	)
}

八. react组件里修改store数据

主要利用react-redux 的一个钩子函数 useDispatch 。它的作用是生成提交action对象的dispatch函数。

app.jsx

import { useDispatch,useSelector } from 'react-redux'
import { add,dec} from './store/modules/counterStore'//导入创建的action方法

function App(){

	const { count } = useSelector(state => state.counter)
	const dispatch = useDispatch()//拿到dispatch函数

	return(
		<div className="App">
			<button onClick={()=> dispatch(dec())}>-</button>
			{count}
			<button onClick={()=> dispatch(add())}>+</button>
		</div>
	)
}

提交action时传参

比如我们每次想累计的数字 是动态传入的。

|add 10| 0 |dec 10|

这个时候,点击add 或者 dec 增、减时需要传递对应的数字作为目标,那么这个目标值在组件中需要提交action时传递过去。

主要修改两处:

  1. 在reducers的同步修改方法中,添加2号参数action,它是一个对象。下边的payload属性会挂载上传来的实参。

store/modules/counterStore.js


	//...
	reducer:{
		addToNum(state,action){
			state.count = action.payload
		}
		dec(state,action){
			state.count = action.payload
		}
	}
	//...
  1. 组件调用点处,传入实参。

app.jsx

//...
function App(){

	//...

	return(
		<div className="App">
			<button onClick={()=> dispatch(dec(10))}>- 10</button>
			{count}
			<button onClick={()=> dispatch(add(10))}>+ 10</button>
		</div>
	)
}

异步状态操作store

从后台获取数据存入到store,异步获取数据,然后再异步修改数据。

接下来,我们做一个案例:动态获取列表数据,然后渲染到页面。

创建对应的子模块store文件:

│├── store                     状态机配置
││├── index.js                 状态机入口
││└── modules                  状态机模块
│ │    └── ListDataStore.js

步骤:

  1. 创建store的写法保持不变,配置好同步修改状态的方法

  2. 单独封装一个函数,在函数内部return一个新函数,在新函数中

    • 2.1 封装异步请求获取数据
    • 2.2 调用同步方法action传入接口数据,并使用dispatch提交
  3. 组件中dispatch的写法保持不变

ListDataStore.js

import { createSlice } form '@reduxjs/toolkit'

const counterStore = createSlice({
	name:'listData',

	//初始化state
	initialState:{
		listData:[]
	},

	//修改状态的方法, 同步方法, 支持直接修改
	reducer:{
		setListData(state,action){
			state.listData = action.payload
		},
		
	}
})

// 解构出来actionCreater 函数
const {setListData} = counterStore.actions


// 异步请求,固定的写法,return 一个异步函数,函数内做ajax请求。
const fetchListData = ()=>{
	return async (dispatch)=>{
		const res = await axios.get('url')
		dispatch(setListData(res.data || []))//提交action时传参
	}
}
// 获取reducer
const reducer = counterStore.reducer

// 以按需导出的方式导出actionCreater
export {fetchListData}

// 以默认的方式导出reducer
export default reducer


核心代码:

// 解构出来actionCreater 函数
const {setListData} = counterStore.actions


// 异步请求,固定的写法,return 一个异步函数,函数内做ajax请求。
const fetchListData = ()=>{
	return async (dispatch)=>{
		const res = await axios.get('url')
		dispatch(setListData(res.data || []))//提交action时传参
	}
}

然后在index.js 里注册引入子store.

页面里如何调用渲染呢?

App.js

import { useDispatch,useSelector } from 'react-redux'
import { add,dec} from './store/modules/counterStore'
import {fetchListData} from './store/modules/listDataStore'//1.
import {useEffect} from 'react'//3.

function App(){

	const { count } = useSelector(state => state.counter)
	const { listData } = useSelector(state => state.listData)//2.
	const dispatch = useDispatch()

	useEffect(()=>{//4.
		dispatch(fetchListData())
	},[dispatch])

	return(
		<div className="App">
			<button onClick={()=> dispatch(dec())}>-</button>
			{count}
			<button onClick={()=> dispatch(add())}>+</button>

			{/*5.*/}
			<ol>
				{listData.map(item => <li key={item.id}>{item.name}</li>)}
			</ol>
		</div>
	)
}
export default App

Redux-DevTools 的安装

谷歌设置,扩展程序。

记录每次action的提交,以及时间。

数据以 tree chart raw(json) 形式的展示。