React-Redux

一、纯函数

1、react 中的纯函数

  • react 中组件就被要求像是一个纯函数(因为还有类组件)
  • redux 中有一个reducer 的概念,也是要求必须是一个纯函数

2、 纯函数的条件

  1. 确定的输入一定会产生确定的输出
  2. 函数在执行过程中,不能产生副作用

3、副作用的概念

表示在执行一个函数时,除了返回函数值之外,还对调用函数产生了附加的影响。比如修改了全局变量,修改参数或者改变了外部的存储

二、Redux 的三大原则

  1. 单一数据源:整个应用程序的state 被存储在一颗object tree 中,并且这个object tree 只存储在一个store 中
  2. state 是只读的:唯一修改state 的方法一定是触发action
  3. 使用纯函数来执行修改:通过reducer 将旧的state 和actions 联系在一起,并且返回一个新的state

三、Redux 的核心概念

1、Store

  • 定义初始化数据,通过reducer 存放到Store 中,所有Store 中的数据都是来源于reducer 函数

2、action

  • Redux 中所有数据的变化,必须通过派发(dispatch)action 来更新
  • action 是一个普通的JavaScript 对象,用来描述这次更新的type 和content
  • 强制使用action 的好处是可以清晰的知道数据到底发生了什么样的变化,所有的数据变化都是可跟踪、可预测的

3、reducer

  • reducer 是一个纯函数
  • reducer 做的事情是将传入的state 和action 结合起来生成一个新的state

示例:

// store/index.js 
import { createStore } from "redux"
import reducer from "./reducer"
const store = createStore(reducer)
export default store

// store/reducer.js 
import * as actionTypes from "./contants"
const initialState = {
    counter: 100
}
function reducer(state = initialState, action) {
    switch(action.type) {
        case actionTypes.ADD_NUMBER:
            return { ...state, counter: state.counter + action.num }
        case actionTypes.SUB_NUMBER:
            return { ...state, counter: state.counter - action.num }
        default: return state
    }
}
export default reducer

// store/actionCreators.js
import * as actionTypes from "./contants"
export const addNumberAction = (num) => ({
    type: actionTypes.ADD_NUMBER,
    num
}) 
export const subNumberAction = (num) => ({
    type: actionTypes.SUB_NUMBER,
    num
}) 

// store/constants.js 
export const ADD_NUMBER = "add_number"
export const SUB_NUMBER = "sub_number"

import store from '../store'
import { addNumberAction } from '../store/actionCreators'
export class Home extends PureComponent {
    constructor() {
        super()
        this.state = {
            counter: store.getState().counter
        }
    }
    componentDidMount() {
        store.subscribe(() => {
            const state = store.getState()
            this.setState({ counter: state.counter })
        })
    }
    addNumber(num) {
        store.dispatch(addNumberAction(num))
    }
    render() {
        const { counter } = this.state
        return (
            

{ counter }

) } }

 四、react-redux

  • npm install react-redux
  • react-redux 是一个高阶组件
  • connect() 是高阶函数,接收两个函数作为参数,返回值是一个高阶组件,接收组件作为参数
  • connect 的第一个参数:将store 中的state 中的数据,映射到props 中
  • connect 的第二个参数:将store 中的dispatch 中的action,映射到props 中
  • 当组件所依赖的store 中的数据发生变化时,组件会重新调用render 函数
npm install react-redux
// index.js
import { Provider } from 'react-redux'
import store from './store'
root.render(
    
        
            
        
    
)
// home.jsx
import { connect } from "react-redux"
export class Home extends PureComponent {
    calcNumber(num, isAdd) {
        if(isAdd) {
            this.props.addNumber(num)
        } else {
            this.props.subNumber(num)
        }
    }
    render() {
        const { counter } = this.props
        return (
            

{ counter }

) } } // 当组件所依赖的store 中的数据发生变化时,组件会重新调用render 函数 // connect 的第一个参数:将store 中的state 中的数据,映射到props 中 const mapStateToProps = (state) => ({ counter: state.counter }) // connect 的第二个参数:将store 中的dispatch 中的action,映射到props 中 const mapDispatchToProps = (dispatch) => ({ addNumber: (num) => dispatch(addNumberAction(num)); subNumber: (num) => dispatch(subNumberAction(num)); }) // connect 本身是一个高阶函数,接收两个函数作为参数 // connect() 返回值是一个新函数,这个新函数是一个高阶组件,接收组件作为参数 export default connect( mapStateToProps, mapDispatchToProps )(Home)

五、redux-thunk 中间件 (处理redux 中的异步操作)

  • 由于在store.dispatch(object) 只能派发对象,所以在redux 中做网络请求的异步操作时,是拿不到请求数据的,所以希望在store.dispatch(function) 派发时可以派发函数(本质派发action 还是要通过dispatch(object))
  • npm install redux-thunk
// 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

// actionCreators.js
export const changeBannersAction = (banners) => {
    type: actionTypes.CHANGE_BANNERS,
    banners
}
export const fetchHomeMultidataAction = () => {
    return function(dispatch, getState) {
        axios.get("http://").then(res => {
            const banners = res.data.data.banner.list
            // dispatch({ type: actionTypes.CHANGE_BANNERS, banners })
            dispatch(changeBannersAction(banners))
        })
    }
}

// banners.jsx
import { connect } from "react-redux"
import { fetchHomeMultidataAction } from "../store/actionCreators.js"
export class Banners extends PureComponent {
    componentDidMount() {
        this.props.fetchHomeMultidata()
    }
    render() {
        return (
            

{ this.props.counter }

) } } const mapStateToProps = (state) => { counter: state.counter } const mapDispatchToProps = (dispatch) => { fetchHomeMultidata() { dispatch(fetchHomeMultidataAction()) } } export default connect( mapStateToProps, mapDispatchToProps )(Banners)

六、combineReducers (redux 中的模块化)

  • combineReducers 函数可以对多个reducer 进行合并
  • combineReducers 的原理:
    • combineReducers 函数是将传入的reducers 合并到一个对象中,最终返回一个combination的函数
    • 在执行combination 函数的过程中,它会通过判断前后返回的数据是否相同来决定返回之前的state 还是新的state
  • 新的state 会触发订阅者发生对应的刷新,而旧的state 就可以有效的自组织订阅者发生刷新
    // combineReducers 的实现原理
    function reducer( state = {}, action ) {
        return {
            counter: counterReducer(state.counter, action),
            home: homeReducer(state.home, action)
        }
    }
// store/index.js
import { combineReducers } from "redux"
import counterReducer from './counter/index.js'
import homeReducerfrom './home'
// 将多个reducer 合并在一起
const reducer = combineReducers({
    counter: counterReducer,
    home: homeReducer
})
const store = createStore(reducer, applyMiddeware(thunk))
export default store

// home.jsx
constructor() {
    super()
    counter: store.getState().counter.counter
}

七、redux-toolkit

  • 安装redux-toolkit:npm install @reduxjs/toolkit      npm install react-redux
  • redux-toolkit 的核心API
  • 1、configureStore: 包装createStore 以提供简化的配置选项和良好的默认值,它可以自动组合slice reducer,添加提供的任何Redux 中间件,redux-thunk 默认包含,并启用Redux DevTools Extension
  • 2、createSlice:接受reducer 函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions
  • 3、createAsyncThunk:接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected 基于该承诺分派动作类型的thunk

1、redux-toolkit 的基本使用

// store/index.js
impoort { configureStore } from '@reduxjs/toolkit'
import counterReducer from './features/counter'
const store = configureStore({
    reducer: {
        counter: counterReducer
    }
})
export default store 

// store/features/counter.js
impoort { createSlice } from '@reduxjs/toolkit'
const counterSlice = createSlice({
    name: "counter",
    initialState: {
        counter: 100
    }
    reducers: {
        addNumber(state, action) {
            const paylaod = action.payload
            state.counter = state.counter + payload
        },
        subNumber(state, { payload }) {
            const paylaod = action.payload
            state.counter = state.counter - payload
        }
    }
})
export const { addNumber, subNumber } = counterSlice.actions
export default counterSlice.reducer

// src/index.js
import { Provider } from 'react-redux'
import App from './App'
import store from './store'
const root = ReactDom.createRoot(document.getElementById('root'))
root.render(
    
        
            
        
    
)

// src/App.jsx
import { connect } from "react-redux"
import { subNumber } from '../store/features/counter'
export class App extends PureComponent {
    addNumber(num) {
        this.props.addNumber(num)
    }
    subNumber(num) {
        this.props.subNumber(num)
    }
    render() {
        const { counter } = this.props
        return (
            

counter: { counter }

) } } const mapStateToProps = (state) => { counter: state.counter.counter } const mapDispatchToProps = (dispatch) => { addNumber(num) { dispatch(addNumber(num)) } subNumber(num) { dispatch(subNumber(num)) } } export default connect(mapStateToProps, mapDispatchToProps)(App)

2、redux-toolkit 进行异步操作

// store/features/home.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
export const fetchHomeMultidataAction = createAsyncThunk("fetchhomedata", async () => {
    const res = await axios.get("http")
    return res.data
})
// fetchHomeMultidataAction 的另一种写法
// export const fetchHomeMultidataAction = createAsyncThunk("fetchhomedata", async (extraParams, { dispatch, getState }) => {
//     const res = await axios.get("http")
//     const banners = res.data.data.banner.list
//     dispatch(changeBanners(banners))
// })
const homeSlice = createSlice({
    name: "home",
    initialState: {
        banners: [],
    },
    reducers: {
        changeBanners(state, { payload }) {
            state.banners = payload
        },
    },
    extraReducers: {
        [fetchHomeMultidataAction.pending](state, action) {
            console.log("异步操作的pending 状态")
        },   
        [fetchHomeMultidataAction.fulfilled](state, { payload }) {
            state.banners = payload.data.banner.list
        },   
        [fetchHomeMultidataAction.rejected](state, action) {
            console.log("异步操作的rejected 状态")
        }
    },
    // extraReducers 的函数写法
    // extraReducers: (builder) => {
    //     builder.addCase(fetchHomeMultidataAction.pending, (state, action) => {
    //         console.log("异步操作的pending 状态")
    //     }).addCase(fetchHomeMultidataAction.fulfilled, (state, { payload }) => {
    //         state.banners = payload.data.banner.list
    //     }).addCase(fetchHomeMultidataAction.rejected, (state, action) => {
    //         console.log("异步操作的rejected 状态")
    //     })
    // }
})
export const { changeBanners } = homeSlice.actions
export default homeSlice.reducer

// src/Home.jsx
export class Home extends PureComponent {
    componentDisMount() {
        this.props.fetchHomeMultidata()
    }
}
const mapDispatchToProps = (dispatch) => {
    fetchHomeMultidata() {
        dispatch(fetchHomeMultidataAction())
    }
}
export default connect(mapDispatchToProps)(Home)

八、connect 的原理

import { PureComponent } from 'react'
import store from "../store"
export default function connect(mapStateToProps, mapDispatchToProps) {
    return function(WrapperComponent) {
        constructor(props) {
            super(props)
            this.state = mapStateToProps(store.getState())
        }
        componentDidMount() {
            this.unsubscribe = store.subscribe(() => {
                this.setState(mapStateToProps(store.getState()))
            })
        }
        componentWillUnmount() {
            this.unsubscribe()
        }
        render() {
            const stateObj = mapStateToProps(store.getState())
            const dispatchObj = mapDispatchToProps(store.dispatch)
            return 
        }
    }
    return NewComponent
}

九、中间件的原理

  • redux-thunk 拦截dispatch 的原理:
  • 当dispatch 进行派发时,redux-thunk 会对该派发做拦截,当dispatch 派发的是一个函数时,会执行该函数并传入两个参数dispatch 和getState

1、对每次派发的action 进行拦截,进行日志打印

  • 这样修改意味着直接修改了dispatch 的调用过程
  • 在调用dispatch 的过程中,真正调用的函数是dispatchAndLog
function patchLogging(store) {
    let next = store.dispatch
    function dispatchAndLog(action) {
        console.log("dispatching:", action)
        next(addAction(5))
        console.log("新的state:", store.getState())
    }
    store.dispatch = dispatchAndLog;
}

2、redux-shunk 的原理

function thunk(store) {
    const next = store.dispatch
    function dispatchThunk(action) {
        if(typeof action === "function"){
            action(store.dispatch, store.getState)
        } else {
            next(action)
        }
    }
    store.dispatch = dispatchThunk
}
thunk(store)

3、applyMiddleware

function applyMiddleware(store, ...fns) {
    fns.forEach(fn => {
        fn(store)
    })
}
export default appliMiddleware

你可能感兴趣的:(redux)