Redux及其相关库使用与源码学习

Redux

纯原生基本使用

redux源码的本质是发布订阅模式。subscribe方法接受函数(订阅函数),当dispatch(action)的时,内部会将上一次的state和本次的action传给reducer函数进行执行,将reducer返回的函数更新为新的state,然后再取出之前subscribe方法订阅好的函数进行执行。

仓库对象提供的核心方法:

  • getState
  • subscribe
  • dispatch

html

xml 复制代码
 <div>
     <p id="counter">0</p>
     <button id="add-button">+</button>
     <button id="minus-button">-</button>
 </div>

js

ini 复制代码
 import { createStore } from 'redux';
 ​
 const counter = document.getElementById('counter');
 const addButton = document.getElementById('add-button');
 const minusButton = document.getElementById('minus-button');
 ​
 // 动作类型
 const ADD = 'ADD';
 const MINUS = 'MINUS';
 ​
 //初始状态数据
 const initState = { number: 0 };
 ​
 function reducer(state = initState, action) {
   switch (action.type) {
     case ADD:
       return { number: state.number + 1 };
     case MINUS:
       return { number: state.number - 1 };
     default:
       return state;
   }
 }
 ​
 // 创建仓库
 const store = createStore(reducer);
 ​
 function render() {
   counter.innerText = store.getState().number;
 } 
 ​
 render();
 store.subscribe(()=>{
   render()
 });
 addButton.addEventListener('click', function () {
   store.dispatch({ type: ADD });
 });
 minusButton.addEventListener('click', function () {
   store.dispatch({ type: MINUS });
 });

核心实现

scss 复制代码
 function createStore(reducer){
     let state;
     const listeners = [];
     
     function getState(){
         return state;
     }
     
     function subscribe(listener){
         listeners.push(listener)
         return ()=>{
             const listenerIndex = listeners.indexOf(listener)
             listeners.splice(listenerIndex,1)
         }
     }
     
     function dispatch(action){
         state = reducer(state,action)
         listeners.forEach((l)=>{ l() })
     }
     
     dispatch({type:"@@redux/init"})
     
     return {
         getState,
         subscribe,
         dispatch
     }
 }
 ​
 export default createStore

结合react使用

javascript 复制代码
 // 动作类型
 const ADD = 'ADD';
 const MINUS = 'MINUS';
 ​
 //初始状态数据
 const initState = { number: 0 };
 ​
 function reducer(state = initState, action) {
     switch (action.type) {
         case ADD:
             return { number: state.number + 1 };
         case MINUS:
             return { number: state.number - 1 };
         default:
             return state;
     }
 }
 ​
 const store = createStore(reducer);
 ​
 class Counter extends React.Component{
     constructor(props){
         super(props)
         this.state = {number:store.getState().number}
     }
 ​
     componentDidMount(){
         this.unsubscribe = store.subscribe(()=>{
             this.setState({number:store.getState().number})
         })
     }
 ​
     componentWillUnmount(){
         this.unsubscribe()
     }
 ​
     render(){
         <div>
             <p>{this.state.number}</p>
             <button onClick={()=>store.dispatch({type:ADD})}>+</button>
             <button onClick={()=>store.dispatch({type:MINUS})}>-</button>
         </div>
     }
 }

优化

  • 将action的创建交给函数调用返回
  • 将action生成函数传递一个函数bindActionCreators,该函数中将action生成器函数包装为store.dispatch(actionCreater)}
csharp 复制代码
  function add (){
      return {type:ADD}
  }
 ​
  function minus (){
      return {type:MINUS}
  }
 ​
 const actionCreators = {add,minus}
 const boundActionCreators = bindActionCreators(actionCreators, store.dispatch)
 ​
 <button onClick={boundActionCreators.add}>+</button>

redux工具方法实现

scss 复制代码
 function bindActionCreators(actionCreators,dispatch){
     const boundActionCreators ={}
     
     for(const key in actionCreators){
         boundActionCreators[key] = function(...args){
             dispatch(actionCreators[key](...args))
         }
     }
     
     return boundActionCreators
 }
 ​
 export default bindActionCreators
  • 合并多个reducer

    当UI派发一个动作action之后,redux源码中不知道这个动作具体会命中那个reducer中的type,所以会全部循环一边所有的reducer并将state和action传递过去,所以如果多个reducer中都有同一个type类型,都会被触发修改(这是redux库这么设计的)。

    javascript 复制代码
     function combineReducers(reducers){
         // 这个combination函数就会被传递给下面的createStore
         return function combination(state={},action){
             let nextState ={}
             for(let key in reducers){
                 let prevStateForKey = state[key]
                 let reducerForKey = reducers[key]
                 prevStateForKey = reducerForKey(nextStateForKey,action)
                 nextState[key] = prevStateForKey
                 // 看似没有state[key] = nextState[key]这行代码,但是实际更新后的state被存放在了外层函数中了
             }
             return nextState
         }
     }
     ​
     export default combineReducers
     ​
     ​
     function createStore(reducer){
         let state;
         const listeners = [];
         
         function getState(){
             return state;
         }
         
         function subscribe(listener){
             listeners.push(listener)
             return ()=>{
                 const listenerIndex = listeners.indexOf(listener)
                 listeners.splice(listenerIndex,1)
             }
         }
         
         function dispatch(action){
             state = reducer(state,action)
             listeners.forEach((l)=>{ l() })
         }
         
         dispatch({type:"@@redux/init"})
         
         return {
             getState,
             subscribe,
             dispatch
         }
     }
     ​
     export default createStore
javascript 复制代码
 import React from 'react';
 import { bindActionCreators } from 'redux';
 import store from '@/store';
 import actionCreators from '@/store/actionCreators/counter1';
 const boundActionCreators = bindActionCreators(actionCreators, store.dispatch);
 //boundActionCreators={add:()=>dispatch({ type: ADD }),minus}
 class Counter extends React.Component {
   constructor(props) {
     super(props);
     this.state = { number: store.getState().counter1.number };
   }
     
   componentDidMount() {
     this.unsubscribe = store.subscribe(() => this.setState({
       number: store.getState().counter1.number
     }));
   }
     
   componentWillUnmount() {
     this.unsubscribe();
   }
     
   render() {
     return (
       <div>
         <p>{this.state.number}</p>
         <button onClick={boundActionCreators.add1}>+</button>
         <button onClick={boundActionCreators.minus1}>-</button>
         <button onClick={() => store.dispatch({ type: 'DOUBLE' })}>DOUBLE</button>
       </div >
     )
   }
 }
 export default Counter;

上面的只使用redux 的代码还是存在冗余:

  1. 每个组件都需自己去订阅触发组件重新渲染的方发和当组件卸载时注销组件重新渲染的方法
  2. 组件需要引入仓库并将仓库中的状态数据映射为组件的state中

React-Redux

基本使用

创建仓库

javascript 复制代码
 import React from 'react'
 import ReactDOM from 'react-dom/client'
 import {Provider} from 'react-redux'
 import store from './store'
 import Counter from './components/client'
 ​
 const root  = ReactDOM.createRoot(document.getElementById('root'))
 root.render(
     <Provider store={store}>
         <Counter>
     </Provider>
 )

类组件中使用

scala 复制代码
 import React from 'react';
 import actionCreators from '../store/actionCreators/counter1';
 import { connect } from 'react-redux';
 class Counter1 extends React.Component {
   render() {
     return (
       <div>
         <p>{this.props.number}</p>
         <button onClick={this.props.add1}>+</button>
         <button onClick={this.props.minus1}>-</button>
       </div >
     )
   }
 }
 //把仓库中的状态映射为组件的属性props对象 仓库到组件的输出
 const mapStateToProps = state => state.counter1;
 //const mapDispatchToProps = dispatch => bindActionCreators(actionCreators, dispatch)
 export default connect(
   mapStateToProps,
   actionCreators //组件的输出,在组件里派发动作,修改仓库  ,这个参数可以是对象也可以是函数,函数则会接受store.dispatch作为参数
 )(Counter1);

函数组件中使用

javascript 复制代码
 import React from 'react';
 import actionCreators from '../store/actionCreators/counter2';
 import { useSelector, useDispatch, useBoundDispatch } from 'react-redux';
 function Counter2() {
   //1.从状态树中获取某一部分状态,进行渲染 2.当仓库中的状态发生改变后会重新渲染组件
   const counter2 = useSelector(state => state.counter2);
   //const dispatch = useDispatch();//store.dispatch
   const { add2, minus2 } = useBoundDispatch(actionCreators);  // useBoundDispatch原生库中并没有实现,自己实现的
   return (
     <div>
       <p>{counter2.number}</p>
       <!-- <button onClick={()=>dispatch(actionCreators.add2())}>+</button> -->
       <button onClick={add2}>+</button>
       <button onClick={minus2}>-</button>
     </div >
   )
 }
 export default Counter2;

基本实现

react-redux内部是借助了react原生提供的createContext API实现的,所以需要先了解createContext 的基本使用才行。

Provider组件实现

javascript 复制代码
 import React from 'react'
 import ReactReduxContext from './ReactReduxContext'
 ​
 export default function Provider(props){
     return (
         <ReactReduxContext.Provider value={{store:props.store}}>
             {props.children}
         </ReactReduxContext.Provider>
     )
 }

ReactReduxContext

javascript 复制代码
 import React from 'react'
 const ReactReduxContext = React.createContext(null)
 export default ReactReduxContext

connect

connect方法本质是一个高阶组件:

javascript 复制代码
 import React from 'react'
 import ReactReduxContext from './ReactReduxContext'
 import {bindActionCreators} from 'redux'
 ​
 function connect(mapStateToProps,mapDispatchToProps){
     return function(Component){
         return class extends React.Component{
             static contextType = ReactReduxContext
             constructor(props,context){
                 super(props)
                 const {store} = context 
                 const {getState, dispatch, subscribe} = store
                 
                 this.state = mapStateToProps(getState())
                 
                 this.unsubscribe = subscribe(()=>{
                     this.setState(mapStateToProps(getState()))
                 })
                 
                 let dispatchProps
                 if(typeof mapDispatchToProps === 'function'){
                     dispatchProps = mapDispatchToProps(dispatch)
                 }else{
                     dispatchProps = bindActionCreators(mapDispatchToProps,dispatch)
                 }
                 this.dispatchProps = dispatchProps
             }
             
             componentWillUnmount(){
                 this.unsubscribe()
             }
             
             render(){
                 return (
                     <Component {...this.props} {...this.state} {...this.dispatchProps}></Component>
                 )
             }
         }
     }
 }

useSelector

scss 复制代码
 import  { useContext, useState, useLayoutEffect, useReducer, useRef } from 'react';
 import ReactReduxContext from '../ReactReduxContext'
 function useSelector(selector){
     const { store } = useContext(ReactReduxContext);
 ​
     const lastSelectedState = useRef(null)
 ​
     // const [state,setState] = useState(0)
     const [,forceUpdate] = useReducer(x=>x+1,0)
 ​
     useLayoutEffect(()=>{
         store.subscribe(()=>{
             // 优化,避免不必要的更新
             let slectedState = selector(store.getState())
             if(lastSelectedState.current === slectedState){
                 return 
             }
             lastSelectedState.current = slectedState
             // setState(state+1)
             
             forceUpdate()
         })
     },[])
 ​
     return selector(store.getState())
 }
 ​
 export default useSelector

useDispatch

javascript 复制代码
 import ReactReduxContext from '../ReactReduxContext'
 function useDispatch(){
     const { store } = useContext(ReactReduxContext);
     return store.dispatch
 }
 ​
 export default useDispatch

useBoundDispatch

工具方法,react-redux中并没有,自己实现的。

javascript 复制代码
 import  { useContext } from 'react';
 import {bindActionCreators} from 'redux'
 ​
 function useBoundDispatch(actionCreators){
     const { store } = useContext(ReactReduxContext);
     return bindActionCreators(actionCreators, store.dispatch)
 }
 ​
 export default useBoundDispatch
相关推荐
SameX4 分钟前
初识 HarmonyOS Next 的分布式管理:设备发现与认证
前端·harmonyos
M_emory_31 分钟前
解决 git clone 出现:Failed to connect to 127.0.0.1 port 1080: Connection refused 错误
前端·vue.js·git
Ciito34 分钟前
vue项目使用eslint+prettier管理项目格式化
前端·javascript·vue.js
成都被卷死的程序员1 小时前
响应式网页设计--html
前端·html
fighting ~1 小时前
react17安装html-react-parser运行报错记录
javascript·react.js·html
老码沉思录1 小时前
React Native 全栈开发实战班 - 列表与滚动视图
javascript·react native·react.js
mon_star°1 小时前
将答题成绩排行榜数据通过前端生成excel的方式实现导出下载功能
前端·excel
Zrf21913184552 小时前
前端笔试中oj算法题的解法模版
前端·readline·oj算法
老码沉思录2 小时前
React Native 全栈开发实战班 - 状态管理入门(Context API)
javascript·react native·react.js
文军的烹饪实验室3 小时前
ValueError: Circular reference detected
开发语言·前端·javascript