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
相关推荐
jacy1 分钟前
图片大图预览就该这样做
前端
林太白3 分钟前
Nuxt3 功能篇
前端·javascript·后端
YuJie5 分钟前
webSocket Manager
前端·javascript
Mapmost20 分钟前
Mapmost SDK for UE5 内核升级,三维场景渲染效果飙升!
前端
Mapmost22 分钟前
重磅升级丨Mapmost全面兼容3DTiles 1.1,3DGS量测精度跃升至亚米级!
前端·vue.js·three.js
wycode29 分钟前
Promise(一)极简版demo
前端·javascript
浮幻云月30 分钟前
一个自开自用的Ai提效VsCode插件
前端·javascript
DevSecOps选型指南31 分钟前
SBOM风险预警 | NPM前端框架 javaxscript 遭受投毒窃取浏览器cookie
前端·人工智能·前端框架·npm·软件供应链安全厂商·软件供应链安全工具
__lll_39 分钟前
Docker 从入门到实战:容器、镜像与 Compose 全攻略
前端·docker