手写Redux

81 阅读8分钟

0.代码

一、reducer的前身:state的传递修改问题

使用上下文完成state的传递,进行读写

import React, {useState, useContext} from 'react'

//0.创建上下文
const appContext = React.createContext(null)
const App = () => {
  const [appState, setAppState] = useState({
    user: {name: 'yuyuan', age: 18}
  })
  //赋值
  const contextValue = {appState, setAppState}
  return (
    <appContext.Provider value={contextValue}>
      <大儿子/>
      <二儿子/>
      <幺儿子/>
    </appContext.Provider>
  )
}
const 大儿子 = () => <section>大儿子<User/></section>
const 二儿子 = () => <section>二儿子<UserModifier/></section>
const 幺儿子 = () => <section>幺儿子</section>
const User = () => {
    //使用上下文进行读取数据
  const contextValue = useContext(appContext)
  return <div>User:{contextValue.appState.user.name}</div>

}

const UserModifier = () => {
    //使用上下文进行读写数据
  const {appState,setAppState} = useContext(appContext)
  const onChange = (e) => {
      //上下文写数据
    setAppState({...appState,user:{...appState.user,name:e.target.value}})
  }
  return <div>
    <input placeholder  ={appState.user.name}
      onChange={onChange}/>
  </div>
}

export default App

二、reducer:规范state创建流程

  • 将修改state数据的过程抽象为函数
const User = () => {
  const contextValue = useContext(appContext)
  return <div>User:{contextValue.appState.user.name}</div>

}

const newState = (state,type,action)=>{
  if(type==='updateState'){
    return {
      ...state,user:{
        ...state.user,...action
      }
    }
  }
  else{
    return state
  }
}

const UserModifier = () => {
  const {appState,setAppState} = useContext(appContext)
  const onChange = (e) => {
    setAppState(newState(appState,'updateState',{name:e.target.value}))
  }
  return <div>
    <input placeholder  ={appState.user.name}
      onChange={onChange}/>
  </div>
}
  • 得到reducer的函数形式
import React, {useState, useContext} from 'react'

const appContext = React.createContext(null)
const App = () => {
  const [appState, setAppState] = useState({
    user: {name: 'yuyuan', age: 18}
  })
  const contextValue = {appState, setAppState}
  return (
    <appContext.Provider value={contextValue}>
      <大儿子/>
      <二儿子/>
      <幺儿子/>
    </appContext.Provider>
  )
}
const 大儿子 = () => <section>大儿子<User/></section>
const 二儿子 = () => <section>二儿子<UserModifier/></section>
const 幺儿子 = () => <section>幺儿子</section>
const User = () => {
  const contextValue = useContext(appContext)
  return <div>User:{contextValue.appState.user.name}</div>

}

//更改函数形式
const reducer = (state,{type,payload})=>{
  if(type==='updateState'){
    return {
      ...state,user:{
        ...state.user,...payload
      }
    }
  }
  else{
    return state
  }
}

const UserModifier = () => {
  const {appState,setAppState} = useContext(appContext)
  const onChange = (e) => {
    //更改使用形式
    setAppState(reducer(appState,{type:'updateState',payload:{name:e.target.value}}))
  }
  return <div>
    <input placeholder  ={appState.user.name}
      onChange={onChange}/>
  </div>
}

export default App

三、dispatch:规范setState的流程

props的使用

为什么需要props,不能单独写一个setState的函数?

  • 因为setState需要原始的数据,就需要useContext,所以需要新建一个组件,去传递props,并且需要改变组件的使用方式。
const 二儿子 = () => <section>二儿子<UserModifier/></section>
// ==>
const 二儿子 = () => <section>二儿子<Wrapper/></section>


// new Component
const Wrapper = ()=> <UserModifier dispatch={dispatch} state={appState}/>

//props传递
const UserModifier = ({dispatch,state})=>{}

dispatch的创建和使用

const Wrapper = ()=>{
  const {appState,setAppState} = React.useContext(appContext)
  const dispatch = (action)=>{
    setAppState(reducer(appState,action))
  }
  return <UserModifier dispatch={dispatch} state={appState}></UserModifier>
}

完整代码

import React, {useState, useContext} from 'react'

const appContext = React.createContext(null)
const App = () => {
  const [appState, setAppState] = useState({
    user: {name: 'yuyuan', age: 18}
  })
  const contextValue = {appState, setAppState}
  return (
    <appContext.Provider value={contextValue}>
      <大儿子/>
      <二儿子/>
      <幺儿子/>
    </appContext.Provider>
  )
}
const 大儿子 = () => <section>大儿子<User/></section>
const 二儿子 = () => <section>二儿子<Wrapper/></section>
const 幺儿子 = () => <section>幺儿子</section>
const User = () => {
  const contextValue = useContext(appContext)
  return <div>User:{contextValue.appState.user.name}</div>

}

const reducer = (state,{type,payload})=>{
  if(type==='updateState'){
    return {
      ...state,user:{
        ...state.user,...payload
      }
    }
  }
  else{
    return state
  }
}

const Wrapper = ()=>{
  const {appState,setAppState} = React.useContext(appContext)
  const dispatch = (action)=>{
    setAppState(reducer(appState,action))
  }
  return <UserModifier dispatch={dispatch} state={appState}></UserModifier>
} 


const UserModifier = ({dispatch,state}) => {
  const onChange = (e)=>{
    dispatch({type:'updateState',payload:{name:e.target.value}})
  }
  return <div>
    <input placeholder={state.user.name} onChange={onChange}></input>
  </div>
}

export default App

四、connect

封装Wrapper,使其由函数创建

import React, {useState, useContext} from 'react'

const appContext = React.createContext(null)
const App = () => {
  const [appState, setAppState] = useState({
    user: {name: 'yuyuan', age: 18}
  })
  const contextValue = {appState, setAppState}
  return (
    <appContext.Provider value={contextValue}>
      <大儿子/>
      <二儿子/>
      <幺儿子/>
    </appContext.Provider>
  )
}
const 大儿子 = () => <section>大儿子<User/></section>
const 二儿子 = () => <section>二儿子<UserModifier /></section>
const 幺儿子 = () => <section>幺儿子</section>
const User = () => {
  const contextValue = useContext(appContext)
  return <div>User:{contextValue.appState.user.name}</div>

}

const reducer = (state,{type,payload})=>{
  if(type==='updateState'){
    return {
      ...state,user:{
        ...state.user,...payload
      }
    }
  }
  else{
    return state
  }
}

//创建Wrapper
const connect = (Component)=>{
//传递自由命名组件中的props
  const Wrapper = (props)=>{
    const {appState,setAppState} = React.useContext(appContext)
    const dispatch = (action)=>{
      setAppState(reducer(appState,action))
    }
    return <Component {...props} dispatch={dispatch} state={appState} />
  } 
  return Wrapper
}

//需要传入的组件
const _UserModifier = ({dispatch,state,children}) => {
  const onChange = (e)=>{
    dispatch({type:'updateState',payload:{name:e.target.value}})
  }
  return <div>
    <input placeholder={state.user.name} onChange={onChange}></input>
  </div>
}

//自由命名组件
const UserModifier = connect(_UserModifier)

export default App

五、redux的出现

问题

前面的代码使用起来没什么大问题,但是有一个问题,就是渲染的次数太多了,每次修改数据,都会让所有的组件都重新渲染。组件数量一多,就非常消耗内存。

思路

  • 改数据后渲染是useState的执行触发的,所以更改数据的函数
const store = {
  appState : {
    user: {name: 'yuyuan', age: 18}
  },
  setAppState : (newState)=>{
    store.appState = newState
    }
}
const App = () => {
  return (
    <appContext.Provider value={store}>
      <大儿子/>
      <二儿子/>
      <幺儿子/>
    </appContext.Provider>
  )
}
  • 让使用appState数据的组件重新读取数据,重新渲染
    • 触发渲染
    • 定位到需要渲染的组件
// 通过useState触发渲染
connect = (Component)=>{
    const Wrapper = (props)=>{
      const {appState,setAppState} = React.useContext(appContext)
      const [,update] = useState({})
      const dispatch = (action)=>{
          update({})
        setAppState(reducer(appState,action))
      }
      return <Component {...props} dispatch={dispatch} state={appState} />
    } 
    return Wrapper
  }
//通过一开始的渲染,将需要定位组件写入数组
const store = {
  appState : {
    user: {name: 'yuyuan', age: 18}
  },
  setAppState : (newState)=>{
    store.appState = newState
    // 数据更新,定位到了组件中的数据也会精准更新
    store.listener.map(fn=>fn(store.appState))
  },
  // listener
  listener : [] ,
  // 写入方法
  addListener(fn){
    store.listener.push(fn)
    return ()=>{
      const index= store.listener.indexOf(fn)
      store.listener.splice(index,1)
    }
  }
}

const connect = (Component)=>{
    const Wrapper = (props)=>{
      const {appState,setAppState} = React.useContext(appContext)
      const [,update] = useState({})
      // 页面初次渲染时,完成listener的写入;
      //update触发Wrapper的重新渲染;
      //Wrapper重新渲染,实现setAppState中新数据的写入
      useEffect(()=>{
        store.addListener(()=>{update({})})
      },[])
      const dispatch = (action)=>{
        setAppState(reducer(appState,action))
      }
      return <Component {...props} dispatch={dispatch} state={appState} />
    } 
    return Wrapper
  }

  • 最后完整代码
//App.jsx
import React from 'react'
import {appContext,connect,store} from './redux'


const App = () => {

  return (
    <appContext.Provider value={store}>
      <大儿子/>
      <二儿子/>
      <幺儿子/>
    </appContext.Provider>
  )
}
const 大儿子 = () => {
  console.log('大儿子执行了',Math.random()); 
  return <section>大儿子<User/></section>}
const 二儿子 = () => {
  console.log('二儿子执行了',Math.random()); 
  return <section>二儿子<UserModifier /></section>}
const 幺儿子 = () => {
  console.log('幺儿子执行了',Math.random()); 
  return <section>幺儿子</section>}

const _User = () => {
  return <div>User:{store.appState.user.name}</div>
}


const _UserModifier = ({dispatch,state,children}) => {
  const onChange = (e)=>{
    dispatch({type:'updateState',payload:{name:e.target.value}})
  }
  return <div>
    <span>{children}</span>
    <input placeholder={state.user.name} onChange={onChange}></input>
  </div>
}

const UserModifier = connect(_UserModifier)
const User = connect(_User)

export default App
//redux.jsx
import React, {useState, useContext} from 'react'
import { useEffect } from 'react'

export const appContext = React.createContext(null)
export const store = {
  appState : {
    user: {name: 'yuyuan', age: 18}
  },
  setAppState : (newState)=>{
    store.appState = newState
    store.listener.map(fn=>fn(store.appState))
  },
  listener : [] ,
  addListener(fn){
    store.listener.push(fn)
    return ()=>{
      const index= store.listener.indexOf(fn)
      store.listener.splice(index,1)
    }
  }
}

const reducer = (state,{type,payload})=>{
    if(type==='updateState'){
      return {
        ...state,user:{
          ...state.user,...payload
        }
      }
    }
    else{
      return state
    }
  }
  
 export const connect = (Component)=>{
    const Wrapper = (props)=>{
      const {appState,setAppState} = React.useContext(appContext)
      const [,update] = useState({})
      useEffect(()=>{
        store.addListener(()=>{update({})})
      },[])
      const dispatch = (action)=>{
        setAppState(reducer(appState,action))
      }
      return <Component {...props} dispatch={dispatch} state={appState} />
    } 
    return Wrapper
  }
  

六、react-redux库中的selector

  • 提供对数据使用时的简化 例如:使用user替代appState.user
  • selector的作用就是传递简化数据使用的方法
// App.jsx中
const User = connect(appState=>{return {user:appState.user}})(({user}) => {
  return  <div>User:{user.name}</div>
})
const UserModifier = connect(appState=>{return {user:appState.user}})(({children,user,dispatch}) => {
  const onChange = (e)=>{
    dispatch({type:'updateState',payload:{name:e.target.value}})
  }
  return <div>
    <span>{children}</span>
    <input placeholder={user.name} onChange={onChange}></input>
  </div>
})
  • 所以selector的API应该怎么写呢?
//redux.jsx中
export const connect = (selector) => (Component)=>{
    const Wrapper = (props)=>{
        const {appState,setAppState} = useContext(appContext)
        const [,update] = useState({})
        // 更改传出的数据
        const data = selector ?  selector(appState) : {appState}
        useEffect(()=>{
            store.addListener(()=>{update({})})
        },[])
        const dispatch = (action)=>{
            setAppState(reducer(appState,action))
        }
        // 传递给组件的发生变化
        return <Component {...props}  {...data} dispatch={dispatch}  />
    } 
    return Wrapper
}
  • 使用selector的时候,实现精准渲染

七、mapToDispatchProps

和上面的selector一样,当我在使用的时候不想写那么长,那么dispatch就要进行优化了。

  • API的使用
// App.jsx中
const UserModifier = connect(appState=>{return {user:appState.user}},
//将updateUser代替dispatch的使用
  dispatch=>{return {updateUser:(attrs)=>dispatch({type:'updateState',payload:attrs})}
})(({children,user,updateUser}) => {
  const onChange = (e)=>{
  //使用替代
    updateUser({name:e.target.value})
  }
  return <div>
    <span>{children}</span>
    <input placeholder={user.name} onChange={onChange}></input>
  </div>
})
  • API的声明
// redux.jsx中
export const connect = (selector,mapDispatchToProps) => (Component)=>{
    const Wrapper = (props)=>{
        const {appState,setAppState} = useContext(appContext)
        const [,update] = useState({})
        const dispatch = (action)=>{
          setAppState(reducer(appState,action))
        }
        const data = selector ?  selector(appState) : {appState}
        const dispatchers = mapDispatchToProps ? mapDispatchToProps(dispatch) : dispatch 
        useEffect(()=>{
            store.addListener(()=>{update({})})
        },[])
        return <Component {...props}  {...data} {...dispatchers}  />
    } 
    return Wrapper
}
  • 将这种方法连贯一下就是
    • 我需要在使用原先方法的时候,简化用法,也就是封装给别名

      • 于是我在使用的时候写好要传入的封装方法,对象中写好别名
    • 在Api的定义中,传入封装方法,更改数据,然后传出

八、关于connect的意义

  • 封装读写的接口,暴露给用户使用

  • 将上面的代码优化一下能得到:

//App.jsx中
const userState = appState=>{return {user:appState.user}}
const userDispatch = dispatch=>{return {updateUser:(attrs)=>dispatch({type:'updateState',payload:attrs})}}

//最后暴露出来的接口:
const userConnect = connect(userState,userDispatch)

const UserModifier = userConnect(({children,user,updateUser}) => {
  const onChange = (e)=>{
    updateUser({name:e.target.value})
  }
  return <div>
    <span>{children}</span>
    <input placeholder={user.name} onChange={onChange}></input>
  </div>
})
const User = userConnect(({user}) => {
  return  <div>User:{user.name}</div>
})
  • 然后单独提取为一个文件

九、封装Provider和createStore

1.createStore()

createStore需要解决什么问题?

  • 在我们的模型中,appState和reducer,原始数据和修改数据的方法是写死的,所以我们需要暴露接口给用户进行编写
  • 接口长什么样子呢? createStore(reducer,initState),由用户去定义reducer函数和initState对象
  • 接口如何编写?
//reducer.jsx中
import React, {useState, useContext} from 'react'
import { useEffect } from 'react'

export const appContext = React.createContext(null)
export const store = {
    //reducer和state都放入store中,且初始值为未定义
  reducer:undefined,
  appState : {},
  setAppState : (newState)=>{
    store.appState = newState
    store.listener.map(fn=>fn(store.appState))
  },
  listener : [] ,
  addListener(fn){
    store.listener.push(fn)
    return ()=>{
      const index= store.listener.indexOf(fn)
      store.listener.splice(index,1)
    }
  }
}

//暴露接口createStore,返回新的store
export const createStore = (reducer,initState)=>{
  store.reducer = reducer
  store.appState = initState
  return store
}


export const connect = (selector,mapDispatchToProps) => (Component)=>{
    const Wrapper = (props)=>{
        const {appState,setAppState} = useContext(appContext)
        const [,update] = useState({})
        const dispatch = (action)=>{
        //将用到reducer的地方改为store.reducer
          setAppState(store.reducer(appState,action))
        }
        const data = selector ?  selector(appState) : {appState}
        const dispatchers = mapDispatchToProps ? mapDispatchToProps(dispatch) : dispatch 
        useEffect(()=>{
            store.addListener(()=>{update({})})
        },[])
        return <Component {...props}  {...data} {...dispatchers}  />
    } 
    return Wrapper
}  
//App.jsx
import {appContext,connect,createStore,store} from './redux'

const reducer = (state,{type,payload})=>{
    if(type==='updateState'){
      return {
        ...state,user:{
          ...state.user,...payload
        }
      }
    }
    else{
      return state
    }
}
const userState =  {
  user: {name: 'yuyuan', age: 18},
  group : {name:'hunan'}
}
createStore(reducer,userState)

2.Provider

Provider干什么用的?怎么用的?

  • 在我们的App.jsx中,我们需要引入上下文然后给用户进行创建上下文组件,为什么我们不直接提供带有上下文的组件?这样用户直接使用就好了,就不用自己定义了。

  • 简而言之就是我们需要封装一下Provider

//redux.jsx
export const Provider = ({store,children})=>{
  return <appContext.Provider value={store}>{children}</appContext.Provider>
}
//App.jsx
import {connect,createStore,store,Provider} from './redux'
const App = () => {
  return (
    <Provider store={store}>
      <大儿子/>
      <二儿子/>
      <幺儿子/>
    </Provider>
  )
}

十、对Redux的概念进行一个总结

写了这么多代码究竟是完成一个什么样的东西?

image.png

  • 简言之就是,我提供对数据的处理方法的一个库,这个库里面有一些很好用的API
  • App中基本就是对接口的使用,redux就是编写接口

connect干了什么?

image.png

export const connect = (selector,mapDispatchToProps) => (Component)=>{
    const Wrapper = (props)=>{
        // 获取原始数据
        const {appState,setAppState} = useContext(appContext)
        const [,update] = useState({})
        
        // 优化数据的更新
        const dispatch = (action)=>{
          setAppState(store.reducer(appState,action))
        }
        
        // 对原始数据的使用进行了简单的封装
        const data = selector ?  selector(appState) : {appState}
        const dispatchers = mapDispatchToProps ? mapDispatchToProps(dispatch) : dispatch 
        
        //触发更新
        useEffect(()=>{
            store.addListener(()=>{update({})})
        },[])
        
        //导出
        return <Component {...props}  {...data} {...dispatchers}  />
    } 
    return Wrapper
}
  • connect是暴露这些接口最直接的一个组件,所有的读写方法都在里面,所以封装为了一个组件,以便传出这些接口
  • 首先,connect获取了原始数据
  • 然后,connect对原始数据的用户的使用需求进行了简单处理
  • 其次,connect为了更改原始的数据,订阅后update了一下
  • 最后,导出组件和接口

最后就是将state和reducer的自定义接口暴露给用户:createState

十一、封装一下redux.jsx保持和官方文档一致

  • 简单处理一下store中的数据,变为redux.jsx中的全局变量,store中只暴露出用户需要的API
import React, {useState, useContext} from 'react'
import { useEffect } from 'react'

export const appContext = React.createContext(null)
let appState = undefined
let reducer = undefined
let listener = []
const  setAppState =  (newState)=>{
  appState = newState
  listener.map(fn=>fn(appState))
}
export const store = {
  getState : ()=>{
    return appState
  },
  dispatch : (action)=>{
    setAppState(reducer(appState,action))
  },
  addListener(fn){
    listener.push(fn)
    return ()=>{
      const index= listener.indexOf(fn)
      listener.splice(index,1)
    }
  }
}

export const createStore = (_reducer,initState)=>{
  reducer = _reducer
  appState = initState
  return store
}



export const connect = (selector,mapDispatchToProps) => (Component)=>{
    const Wrapper = (props)=>{
        // const {appState,setAppState} = useContext(appContext)
        const [,update] = useState({})
        
        const data = selector ?  selector(appState) : {appState}
        const dispatchers = mapDispatchToProps ? mapDispatchToProps(store.dispatch) : store.dispatch 
        useEffect(()=>{
            store.addListener(()=>{update({})})
        },[])
        return <Component {...props}  {...data} {...dispatchers}  />
    } 
    return Wrapper
}
  

export const Provider = ({store,children})=>{
  return <appContext.Provider value={store}>{children}</appContext.Provider>
}

waiting 未完待续

十二、Redux支持函数action

目前的Redux不支持异步的action

解决一下这个问题

十三、Redux支持Promise

十四、中间件

十五、Redux库的总结:Redux基本API的使用