redux和react没有直接的关系,你完全可以在React, Angular, Ember, jQuery, or vanilla JavaScript中使用Redux
一、 Redux三大核心理念
- Store :定义数据state 只读
- action :通过action来更新数据
- reducer: 纯函数来执行修改
1.1 什么是 Redux Toolkit?
- 官方推荐的编写 Redux 逻辑的方法。
@reduxjs/toolkit
包封装了核心的redux
包,包含我们认为构建 Redux 应用所必须的 API 方法和常用依赖。 Redux Toolkit 集成了我们建议的最佳实践,简化了大部分 Redux 任务,阻止了常见错误,并让编写 Redux 应用程序变得更容易 - 要写任何的 Redux 逻辑,你都应该使用 Redux Toolkit 来编写代码
- 创建 Redux Toolkit 来消除手写 Redux 逻辑中的「样板代码」,防止常见错误,并提供简化标准 Redux 任务的 API
- 创建 Redux Toolkit 来消除手写 Redux 逻辑中的「样板代码」,防止常见错误,并提供简化标准 Redux 任务的 API
1.2 redux基本使用
- 使用
npm install redux
安装redux
简单基本示例使用
//store.js // 进行引入 const { createStore } = require("redux") // 定义初始化数据 const initState = { name: "林夕", age: 18 } // 定义reducer函数:纯函数 function reducer(state = initState, action) { // 参数state:store目前保存的state // 参数action:本次次需要更新Waction(dispatchi入iaction) // console.log(state, action); // 有数据更新进行更新,返回新state switch (action.type) { case "name_change": return { ...state, name: action.name } case "age_change": return { ...state, age: action.age } default: return state; } // 返回值就是本次存储的initState值:即store值 return state } // 创建store const store = createStore(reducer) module.exports = store
逐步优化代码,监听订阅修改使用subscribe
1.3 subscribe(listener)监听数据是否变化
变化监听器。每当 dispatch action 的时候就会执行,state 树中的一部分可能已经变化。你可以在回调函数里调用 getState() 来拿到当前 state
1.3.1 参数
listener
(Function): 每当 dispatch action 的时候都会执行的回调。state 树中的一部分可能已经变化。你可以在回调函数里调用getState()
来拿到当前 state。store 的 reducer 应该是纯函数,因此你可能需要对 state 树中的引用做深度比较来确定它的值是否有变化。
1.3.2返回值
(Function): 一个可以解绑变化监听器的函数。
const store = require("./store");
// 定义动态生成action可以新建新文件---始
//这里的type名字同样可以进行抽取使用。
const nameActionDispatch = (name) => ({ type: "name_change", name })
const ageActionDispatch = (age) => ({ type: "age_change", age })
// 定义动态生成action可以新建新文件---末
//订阅监听
const unsubscribe = store.subscribe(() => {
console.log("监听修改", store.getState());
})
// 修改store中的数据:必须action
store.dispatch(nameActionDispatch("Go to swimming pool"))
store.dispatch(ageActionDispatch(22))
//取消订阅监听
unsubscribe()
store.dispatch(nameActionDispatch("取消订阅"))
- 其上代码我们可以进行拆分逻辑抽取单独文件进行优化,做到编写一次复用N次进行优化。
- 同时遵从三大核心理念进行拆分文件,有需要可以拆分一个常量文件用来导出type名;
tips
- 注意:node中对ES6模块化的支持
- 从node v13.2.0开始,node才对ES6模块化提供了支持
- node v13.2.0之前,需要进行如下操作:
- 在package.json中添加属性:"type":"module”;
- 在执行命令中添加如下选项:node --experimental-modules src/index.js;
- node v13.2.0之后,只需要进行如下操作:在package.json中添加属性:"type":"module”;
- 注意:导入文件时,需要跟上.js后缀名:
二、 react中初次使用redux实例练习
- 使用
create-react-app '项目名'
创建项目 - 使用
npm install redux
安装redux
新建目录
APP.jsx文件
import React, { PureComponent } from 'react'
import Home from './peages/Home'
import User from './peages/User'
import { HOC } from './utils/higherOrderComponent'
const Homes = HOC(Home)
const Users = HOC(User)
// 这里懒得拆分单文件了,使用函数创建了一个组件
const Header = HOC((props)=>{
return(<h2>我是APP组件:{props.count}</h2>)
})
export class App extends PureComponent {
render() {
return (
<>
<Header/>
<Homes />
<div>
<Users />
</div>
</>
)
}
}
export default App
Home.jsx文件子组件
import React, { PureComponent } from 'react'
import store from '../store'
import { addNumAction } from '../store/actionCreators'
export class Home extends PureComponent {
add(num){
store.dispatch(addNumAction(num))
}
render() {
//高阶组件进行拦截后的传输参数
const { count } = this.props
return (
<>
我是home增加计数:{ count }
<div>
<button onClick={e=>this.add(1)}>+1</button>
<button onClick={e=>this.add(5)}>+5</button>
<button onClick={e=>this.add(10)}>+10</button>
</div>
</>
)
}
}
export default Home
User.jsx文件子组件
import React, { PureComponent } from 'react'
import store from '../store'
import { reduceNumAction } from '../store/actionCreators'
export class User extends PureComponent {
reduceClick(num) {
store.dispatch(reduceNumAction(num))
}
render() {
//高阶组件进行拦截后的传输参数
const { count } = this.props
return (
<>
我是User减少计数:{count}
<div>
<button onClick={e => this.reduceClick(1)}>-1</button>
<button onClick={e => this.reduceClick(5)}>-5</button>
<button onClick={e => this.reduceClick(10)}>-10</button>
</div>
</>
)
}
}
export default User
higherOrderComponent.js高阶组件不明白高阶组件的使用请移步React的高阶组件
import {PureComponent} from 'react'
import store from '../store'
// 这里封装了一个高阶组件,相当进行拦截操作。
export function HOC(WrappedComponent){
class NewWrappedComponent extends PureComponent{
constructor() {
super()
this.state = {
count:store.getState().counter
}
}
componentDidMount() {
store.subscribe(() => {
const state = store.getState()
//实现更新因为这里高阶组件使用setState数据变化都会进行更新界面
this.setState({count:state.counter})
})
}
render(){
return(
<>
<WrappedComponent {...this.props} {...this.state}/>
</>
)
}
}
return NewWrappedComponent
}
redux结构划分
源自coderwhy学习
store/index.js入口文件
import { createStore } from 'redux'
import reducer from './reducer'
const store = createStore(reducer)
export default store
reducer.js 纯函数来执行修改state
import { ADD_COUNTER, REDUCE_COUNTER } from './constants'
const initState = {
counter: 10
}
function reducer(state = initState, action) {
switch (action.type) {
case ADD_COUNTER:
let counter = state.counter + action.counter
return { ...state, counter }
case REDUCE_COUNTER:
let reduceCounter = state.counter - action.counter
return { ...state, counter: reduceCounter }
default:
return state;
}
}
export default reducer
actionCreators.js执行action函数抽取单文件
import { ADD_COUNTER,REDUCE_COUNTER } from './constants'
export const addNumAction = (counter) => ({ type: ADD_COUNTER, counter })
export const reduceNumAction = (counter) => ({ type: REDUCE_COUNTER, counter })
constants.js 用来存储改type
export const ADD_COUNTER = "add_counter"
export const REDUCE_COUNTER = "reduce_counter"
三、 React-Rudex
上示例中使用了redux,但是对于监听store数据改变 派发事件(
dispatch
),等这些操作出现公共复杂引入调用,redux官方帮助我们提供了 react-redux 的库,可以直接在项目中使用,并且实现的逻辑会更加的严谨和高效
npm install react-redux
1 .使用 Provider
透传 Store
使用 <Provider>
组件包裹 <App>
组件,并将 Redux store 作为 prop 传递给 <Provider>
组件。之后,应用程序中的每个组件都可以在需要时能够访问到 Redux store
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from "react-redux"
import store from './store'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
// 使用 `<Provider>` 组件包裹 `<App>` 组件
// 并把 Redux store 作为 prop 传入
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
2. connect()高阶函数
connect()
函数将 React 组件连接到 React store。
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)
mapStateToProps
和mapDispatchToProps
分别处理 Redux store 的state
和dispatch
。state
和dispatch
将作为第一个参数提供给mapStateToProps
或mapDispatchToProps
函数。- 在内部,
mapStateToProps
和mapDispatchToProps
的返回值分别称为stateProps
和dispatchProps
。如果定义了,它们将作为第一个和第二个参数提供给mergeProps
,其中第三个参数将是ownProps
。组合的结果通常称为mergedProps
,将提供给连接的组件。
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import { powerNumAction } from '../store/actionCreators'
export class Home extends PureComponent {
poweClick(num){
this.props.powerNumAction(num)
}
render() {
const { counter } = this.props
return (
<>
Home账号文章:{counter}
<div>
<button onClick={e => this.poweClick(2)}>*2</button>
<button onClick={e => this.poweClick(3)}>*3</button>
<button onClick={e => this.poweClick(4)}>*4</button>
</div>
</>
)
}
}
//像高阶组件传输参数一样进行编写上文使用
const mapStateToProps = (state) => ({ counter: state.counter })
const mapDispatchToProps = (dispatch) => {
return {
// dispatch 普通的 action
//来源参考下图
powerNumAction: (num) => dispatch(powerNumAction(num)),
}
}
//connect()返回值是一个高阶组件,
export default connect(mapStateToProps,mapDispatchToProps)(Home)
上文用例也可用Hooks来使用,但是要在函数式组件中使用
更新数据同时可以使用异步请求获取数据存储在state中:异步获取数据一般在生命周期componentDidMount中进行操作一般做法和上例一样。
优化后使用中间件来进行操作数据转发
3.、 异步数据管理redux-thunk
须知:异步请求操作数据一般用在网络请求等应用场景,因此我们需要一个中间件来进行异步操作。
- 这个中间件的目的是在
dispatch的action
和最终达到的reducer
之间,扩展一些自己的代码;- 比如日志记录、调用异步接口、添加代码调试功能等等;
安装:npm install redux-thunk
- 上文使用
dispatch({...})
是需要进行传入对象 - 但是需要在
store.dispatch(function)
中派发函数:- 本质上使用
redux-thunk
工具库会对dispatch
进行拦截,该库会进行判断传入的参数具体是什么类型,若是函数就进行执行函数
- 本质上使用
- 进行网络请求一般进行抽取,但是上文提出是在组件中进行网络请求后进行修改,这里提出示例进行抽取
- 放在redux中进行数据请求后分发
使用applyMiddleware | Redux 中文官网
因此使用redux-trunk做为中间件使用
store/index.js
import { createStore,applyMiddleware } from "redux";
import {thunk} from 'redux-thunk'
import { reducer } from "./reducer";
const store = createStore(reducer,applyMiddleware(thunk))
export default store
store/actionCreators.js
import { COUNT_CHANGE,ARRAY_CHANGE } from "./constants";
import axios from 'axios'
export const countChange=(count)=>({type:COUNT_CHANGE,count})
export const arrayChange=(arrayList)=>({type:ARRAY_CHANGE,arrayList})
export const fetchNetData=()=>{
return function(dispatch, getState){
axios.get("接口地址").then(({data})=>{
console.log(data);
dispatch(arrayChange(data.data.list))
})
}
}
其它调用逻辑都是和上文介绍相同
4、combineReducers(reducers)
当我们在项目中如果多个模块使用数据状态管理那么就需要多个文件进行拆分不同store最后再合并成一个出口去使用,类似于vuex中的模块化管理类似。需要用到该函数
随着应用变得越来越复杂,可以考虑将 reducer 函数 拆分成多个单独的函数,拆分后的每个函数负责独立管理 state 的一部分
由 combineReducers()
返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers()
时对应的 key 进行命名
5. redux-devtools 在redux中继承devtools
import { createStore,applyMiddleware,compose, combineReducers } from "redux";
import {thunk} from 'redux-thunk'
import { reducer } from "./reducer"
//开启redux工具调试
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
//这里只演示了单个作为模块使用,多个的话可以继续对象追加包括,取用也需要用此名字
const rootReducer = combineReducers({count: reducer})//像vue中模块化管理类似需要进行包括一层
const store = createStore(rootReducer,composeEnhancers(applyMiddleware(thunk)))
export default store
四、 Redux Toolkit
本文知识是对前两章文章的内容的简写方式,前两章是redux的基础使用。
@reduxjs/toolkit包仅仅是对redux的代码进行封装;在react中使用还是需要用react-redux包将两者联系在一起
npm install @reduxjs/toolkit react-redux
- Redux Toolkit的核心API主要是如下几个
configureStore
:包装createStore以提供简化的配置选项和良好的默认值。它可以自动组合你的 slice reducer,添加你提供的任何 Redux中间件,redux-thunk默认包含,并启用 Redux DevTools Extension。createSlice
: 接受reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions.createAsyncThunk
: 接受一个动作类型字符电和一个返回承诺的函数,并生成一个pending/fulfilled/rejected基于该承诺分派动作类型的 thunk
import { configureStore } from '@reduxjs/toolkit'
export default configureStore({
reducer: {}
})
1 .createSlice
createSlice
接收一个包含三个主要选项字段的对象:
createSlice返回值是一个对象,包含所有的actions
name
:用户标记slice的名词字符串,将用作生成的 action types 的前缀initialState
:reducer 的初始 state初始化值reducers
:相当于reducer函数- 对象类型,并且可以添加很多的函数
- 函数类似于redux原来reducer中的一个case语句
- 函数的参数
- 参数一:state
- 参数二:调用这个action时,传递的action参数;
2.configureStore
用于创建store对象,常见参数如下:
- reducer,将slice中的reducer可以组成一个对象传入此处
- middleware: 可以使用参数,传入其他的中间件
- devTools: 是否配置devTools工具,默认为true;
app.jsx组件
import React, { PureComponent } from 'react'
import Home from './Home'
import { connect } from 'react-redux'
import { ageChange, nameChange } from './store/userState'
export class App extends PureComponent {
render() {
const { name, age } = this.props
return (
<>
<h2>我是APp组件 :{name}-{age}</h2>
<button onClick={e=>this.props.nameChange("测试一下")}>修米关系</button>
<button onClick={e=>this.props.ageChange(123)}>修米关系2</button>
<Home />
</>
)
}
}
const mapStateToProps = (state) => ({
name: state.userState.name,
age: state.userState.age,
})
const mapDispatchToProps = (dispatch) => {
return {
nameChange: (name) => dispatch(nameChange(name)),
ageChange: (num) => dispatch(ageChange(num)),
}
}
export default connect(mapStateToProps,mapDispatchToProps)(App)
store/index.js
import { configureStore } from '@reduxjs/toolkit'
import userState from './userState'
export default configureStore({
reducer: {userState}
})
store/userState.js
import { createSlice } from '@reduxjs/toolkit'
const userStateSlice = createSlice({
name: "useState",
initialState: {
name: "林夕",
age: 18
},
reducers: {
nameChange(state, action) {
state.name = action.payload
},
ageChange(state, action) {
state.age = action.payload
}
}
})
export const { nameChange, ageChange } = userStateSlice.actions
export default userStateSlice.reducer
3.createAsyncThunk :Redux Toolkit的异步操作
- 当createAsyncThunk创建出来的action被dispatch时,会存在三种状态
- pending: action被发出,但是还没有最终的结果
- fulfilled: 获取到最终的结果 (有返回值的结果)
- rejected: 执行过程中有错误或者抛出了异常
createSlice
还接收一个叫 extraReducers
的选项,可以让同一个 slice reducer 监听其他 action types。这个字段应该是一个带有 builder
参数的回调函数,我们可以调用 builder.addCase(actionCreator, caseReducer)
来监听其他 actions 用法一:官方推荐
用法二
4.手写react-redux中的 connect()函数
import { PureComponent } from "react";
import store from '../store'
export default function customConnet(mapStateToProps, mapDispatchToProps) {
return function (WrappedComponent) {
class NewCom extends PureComponent {
constructor(props) {
super(props)
this.state = mapStateToProps(store.getState())
}
componentDidMount() {
this.unsubscribe = store.subscribe(() => {
this.setState(mapStateToProps(store.getState()))
// this.forceUpdate()
})
}
componentWillUnmount() {
this.unsubscribe()
}
render() {
const state = mapStateToProps(store.getState())
const dispatch = mapDispatchToProps(store.dispatch)
return <WrappedComponent {...this.props} {...state}{...dispatch} />
}
}
return NewCom
}
}
页面组件
import React, { PureComponent } from 'react'
import customConnet from './HOC/customConnet'
import { nameChange } from './store/userState'
export class Custom extends PureComponent {
render() {
const { name } = this.props
return (
<div>
{name}
<button onClick={e=>this.props.nameChange("我是自定义组件高阶")}>我是自定义高阶函数修改</button>
</div>
)
}
}
const mapStateToProps = (state) => ({
name: state.userState.name,
})
const mapDispatchToProps = (dispatch) => {
return {
nameChange: (name) => dispatch(nameChange(name)),
}
}
export default customConnet(mapStateToProps, mapDispatchToProps)(Custom)
对于导入的import store from '../store'
文件耦合性太高我们可以进行一些优化使用前文的context上下文
来传递store路径的解耦
4.1 HOC/hocContext.js
import { createContext } from "react";
export const HOCContext = createContext()
4.2 HOC/customConnet.js
import { PureComponent } from "react";
import { HOCContext } from "./hocContext";
export function customConnet(mapStateToProps, mapDispatchToProps) {
return function (WrappedComponent) {
class NewCom extends PureComponent {
constructor(props, context) {
super(props)
this.state = mapStateToProps(context.getState())
}
componentDidMount() {
this.unsubscribe = this.context.subscribe(() => {
this.setState(mapStateToProps(this.context.getState()))
// this.forceUpdate()
})
}
componentWillUnmount() {
this.unsubscribe()
}
render() {
const state = mapStateToProps(this.context.getState())
const dispatch = mapDispatchToProps(this.context.dispatch)
return <WrappedComponent {...this.props} {...state}{...dispatch} />
}
}
NewCom.contextType = HOCContext
return NewCom
}
}
4.3 HOC/index.js
export { customConnet } from "./customConnet"
export { HOCContext } from "./hocContext"
4.4 src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import store from './store'
import { HOCContext } from "./HOC";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<HOCContext.Provider value={store}>
<App />
</HOCContext.Provider>
</React.StrictMode>
);
5、Redux Toolkit的数据不可变性★
- 无论是类组件中的state,还是redux中管理的state;
- 在整个JavaScript编码过程中,数据的不可变性都是非常重要的;
- 前面的章节示例中使用了很多的类似浅拷贝的数据操作,但是尽管如此也可能会出现问题
- 比如过大的对象,进行浅拷贝也会造成性能的浪费;
- 比如浅拷贝后的对象,在深层改变时,依然会对之前的对象产生影响;
- 事实上Redux Toolkit底层使用了immerjs和immutable的一个库来保证数据的不可变性
- 为了节约内存,又出现了一个新的算法: Persistent Data Structure(持久化数据结构或一致性数据结构);
- 用一种数据结构来保存数据
- 当数据被修改时,会返回一个对象,但是新的对象会尽可能的利用之前的数据结构而不会对内存造成浪费
五、react中state管理
- 方式一:组件中自己的state管理
- 方式二:Context数据的共享状态
- 方式三:Redux管理应用状态 建议使用
- UI相关的组件内部可以维护的状态,在组件内部自己来维护:
- 大部分需要共享的状态,都交给redux来管理和维护:
- 从服务器请求的数据(包括请求的操作),交给redux来维护: