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
相关推荐
芥子沫10 分钟前
Safari-常用快捷键(IPadOS版本)
前端·safari
wgc8917840 分钟前
Zabbix短信告警示例
前端·chrome·zabbix
逝缘~43 分钟前
uni-icons自定义图标详细步骤及踩坑经历
前端·javascript·css·vue.js·uni-app·html
Shinobi_Jack1 小时前
Go调试工具—— Delve
前端·后端·go
QGC二次开发1 小时前
Vue3:快速生成模板代码
前端·javascript·vue.js·前端框架·vue
天涯学馆1 小时前
Svelte Store与Vuex:轻量级状态管理对比
前端·vue·vuex·svelte
时光书签2 小时前
通过http地址下载文件
服务器·前端·c#·asp.net
无名前端小白2 小时前
React-Native 中使用 react-native-image-crop-picker 在华为手机上不能正常使用拍照功能
react native·react.js·华为·鸿蒙
专注VB编程开发20年3 小时前
如何保存网站CSS和JS中的图片?网页另存为本地显示不正常
前端·javascript·css
丶重明3 小时前
【2024】前端学习笔记9-内部样式表-外部导入样式表-类选择器
前端·笔记·学习