手写redux简易源码

92 阅读8分钟

跨组件传值:

先用useContext跨组件传公共的值,并通过useState设置修改变量,重新渲染组件,代码如下:

import React, {useState, useContext} from 'react';
const appContext = React.createContext(null)
function App() {
  const [appState, setAppState] = useState({
    user: {name: 'frank', age: 18}
  })
  const contextValue = {appState, setAppState}
  return (
    <appContext.Provider value={contextValue}>
      <One/>
      <Two/>
      <Three/>
    </appContext.Provider>
  );
}
const One = () => <section>one <User/></section>
const Two = () => <section>two<UserModifie>内容</UserModifie></section>
const Three = <section>three{group.name}</section>
const User = () => {
  const contextValue = useContext(appContext)
  return <div>User:{contextValue.appState.user.name}</div>
}
const UserModifie = () => {
   const contextValue = useContext(appContext)
   const onChange = (e) => {
     contextValue.appState.user.name = e.target.value
     contextValue.setAppState({...contextValue.appState})
   }
   return (
    <div>
      <input value={contextValue.appState.user.name} onChange={onChange}/>
    </div>
  )
}

reducer

规范State的创建流程,reducer是一个函数,规范创建state的过程,代码如下:

import React, {useState, useContext} from 'react';
const reducer = (state, { type, payLoad }) => {
    if (type === 'updateUser') {
        return {
        ...state,
        user: {
            ...state.user,
            ...payLoad
        }
        }
    } else {
        return state
    }
}
const appContext = React.createContext(null)
function App() {
  const [appState, setAppState] = useState({
    user: {name: 'frank', age: 18}
  })
  const contextValue = {appState, setAppState}
  return (
    <appContext.Provider value={contextValue}>
      <One/>
      <Two/>
      <Three/>
    </appContext.Provider>
  );
}
const One = () => <section>one <User/></section>
const Two = () => <section>two<UserModifie>内容</UserModifie></section>
const Three = <section>three{group.name}</section>
const User = () => {
  const contextValue = useContext(appContext)
  return <div>User:{contextValue.appState.user.name}</div>
}
const UserModifie = () => {
   const {appState, setAppState} = useContext(appContext)
   const onChange = (e) => {
   const NewState = reducer(appState, {type: 'updateUser', payLoad:{name: e.target.value}})
     contextValue.setAppState(NewState)
   }
   return (
    <div>
      <input value={contextValue.appState.user.name} onChange={onChange}/>
    </div>
  )
}

dispatch

规范setState流程。修改state变量,每次都要调用setState,把调用setState的代码封装在dispatch里面,但是如果dispatch封装函数要访问到appState, setAppState,所以必须把dispatch放到一个组件里面,这就是connect,它是一个高阶组件(接收一个组件,返回一个新的组件),代码如下:

import React, {useState, useContext} from 'react';
const reducer = (state, { type, payLoad }) => {
    if (type === 'updateUser') {
        return {
        ...state,
        user: {
            ...state.user,
            ...payLoad
        }
        }
    } else {
        return state
    }
}
const appContext = React.createContext(null)
const connect = (Component) => {
    const Wrapper = (props) => {
      const {appState, setAppState} = useContext(appContext)
      const dispatch = (action) => {
        setAppState(reducer(appState, action))
      }
      return <Component {...props} state = {appState} dispatch = {dispatch}/>
    }
    return Wrapper
}
function App() {
  const [appState, setAppState] = useState({
    user: {name: 'frank', age: 18}
  })
  const contextValue = {appState, setAppState}
  return (
    <appContext.Provider value={contextValue}>
      <One/>
      <Two/>
      <Three/>
    </appContext.Provider>
  );
}
const User = () => {
  const contextValue = useContext(appContext)
  return <div>User:{contextValue.appState.user.name}</div>
}
const _UserModifie = ({dispatch, state, children}) => {
   const onChange = (e) => {
     dispatch({type: 'updateUser', payLoad:{name: e.target.value}})
   }
   return (
    <div>
      {children}
      <input value={state.user.name} onChange={onChange}/>
    </div>
  )
}
const UserModifie = connect(_UserModifie)
const One = () => <section>one <User/></section>
const Two = () => <section>two<UserModifie>内容</UserModifie></section>
const Three = <section>three{group.name}</section>

state改变,用到state组件才重新渲染

现在代码的性能还有很多问题,如果state改变一个属性值,全部组件都会重新渲染,因为app组件执行setState,组件函数会重新执行,它的全部子组件都会重新渲染。解决办法是哪个组件用到了state里的某个值,并且那个值改变了,才重新执行渲染,代码如下:

import React, {useState, useContext, useEffect} from 'react';
const reducer = (state, { type, payLoad }) => {
    if (type === 'updateUser') {
        return {
        ...state,
        user: {
            ...state.user,
            ...payLoad
        }
        }
    } else {
        return state
    }
}
const store = {
  state: {
    user: {name: 'frank', age: 10},
  },
  setState(newState) {
    store.state = newState
    store.linsteners.map(fn => fn(store.state))
  },
  linsteners: [], // 用到connect的高阶组件,订阅更新,某个组件里面的state值更新了,遍历,其它组件也会更新
  subscribe(fn) {
      store.listeners.push(fn)
      return () => {
        const index = store.listeners.index(fn)
        store.listeners.splice(index, 1)
      }
  }
}
const appContext = React.createContext(null)
function App() {
  return (
    <appContext.Provider store={store}>
      <One/>
      <Two/>
      <Three/>
    </appContext.Provider>
  );
}
const connect = (Component) => {
    const Wrapper = (props) => {
      const {state, setState} = useContext(appContext)
      const [, update] = useState({})
      useEffect(() => {
        store.subscribe(() => {
           update({}) // 强制刷新组件
        })
      }, [])
      const dispatch = (action) => {
        setState(reducer(appState, action))
      }
      return <Component {...props} state = {state} dispatch = {dispatch}/>
    }
    return Wrapper
}
const User = connect(({dispatch, state}) => {
  return <div>User:{state.user.name}</div>
})
const UserModifie = connect(({dispatch, state, children}) => {
   const onChange = (e) => {
     dispatch({type: 'updateUser', payLoad:{name: e.target.value}})
   }
   return (
    <div>
      {children}
      <input value={state.user.name} onChange={onChange}/>
    </div>
  )
})
const One = () => <section>one <User/></section>
const Two = () => <section>two<UserModifie>内容</UserModifie></section>
const Three = <section>three{group.name}</section>

生成redux.js文件

现在把关于redux的内容提取出来,新建redux.js,里面代码如下

import React, {useState, useContext} from 'react';
export const store = {
  state: {
    user: {name: 'frank', age: 10},
  },
  setState(newState) {
    store.state = newState
    store.linsteners.map(fn => fn(store.state))
  },
  linsteners: [], // 用到connect的高阶组件,订阅更新,某个组件里面的state值更新了,遍历,其它组件也会更新
  subscribe(fn) {
      store.listeners.push(fn)
      return () => {
        const index = store.listeners.index(fn)
        store.listeners.splice(index, 1)
      }
  }
}
const reducer = (state, { type, payLoad }) => {
    if (type === 'updateUser') {
        return {
        ...state,
        user: {
            ...state.user,
            ...payLoad
        }
        }
    } else {
        return state
    }
}
export const connect = (Component) => {
    const Wrapper = (props) => {
      const {state, setState} = useContext(appContext)
      const [, update] = useState({})
      useEffect(() => {
        store.subscribe(() => {
           update({}) // 强制刷新组件
        })
      }, [])
      const dispatch = (action) => {
        setState(reducer(appState, action))
      }
      return <Component {...props} state = {state} dispatch = {dispatch}/>
    }
    return Wrapper
}
export const appContext = React.createContext(null)

app.js里面的代码,就清理为:

import React from 'react';
import {connect, appContent, store} from './redux'
function App() {
  return (
    <appContext.Provider store={store}>
      <One/>
      <Two/>
      <Three/>
    </appContext.Provider>
  );
}

const User = connect(({dispatch, state}) => {
  return <div>User:{state.user.name}</div>
})
const UserModifie = connect(({dispatch, state, children}) => {
   const onChange = (e) => {
     dispatch({type: 'updateUser', payLoad:{name: e.target.value}})
   }
   return (
    <div>
      {children}
      <input value={state.user.name} onChange={onChange}/>
    </div>
  )
})
const One = () => <section>one <User/></section>
const Two = () => <section>two<UserModifie>内容</UserModifie></section>
const Three = <section>three{group.name}</section>

到现在为止,redux的结构基本完成,里面包含store、reducer、connect、appContext。

React-Redux里面的Selector

它是connect函数的选择函数参数,让组件只获取局部state(只获取组件要用到的属性),并对组件所用到的变量选择,只用用到的属性变化了,组件才重新渲染。redux.js代码修改如下:

import React, {useState, useContext} from 'react';
const changed = (oldState, newState) => {
  let result = false
  Object.keys(newState).forEach(key => {
    if (newState[key] !== oldState[key]) {
      result = true
    }
  })
  Object.keys(oldState).forEach(key => {
    if (oldState[key] !== newState[key]) {
      result = true
    }
  })
  return result
}
export const store = {
  state: {
    user: {name: 'frank', age: 10},
  },
  setState(newState) {
    store.state = newState
    store.linsteners.map(fn => fn(store.state))
  },
  linsteners: [], // 用到connect的高阶组件,订阅更新,某个组件里面的state值更新了,遍历,其它组件也会更新
  subscribe(fn) {
      store.listeners.push(fn)
      return () => {
        const index = store.listeners.index(fn)
        store.listeners.splice(index, 1)
      }
  }
}
const reducer = (state, { type, payLoad }) => {
    if (type === 'updateUser') {
        return {
        ...state,
        user: {
            ...state.user,
            ...payLoad
        }
        }
    } else {
        return state
    }
}
export const connect = (selector) => (Component) => {
    const Wrapper = (props) => {
      const {state, setState} = useContext(appContext)
      const [, update] = useState({})
      const data = selector ? selector(state) : {state}
      useEffect(() => {
        store.subscribe(() => {
        const newData = selector ? selector(state) : {state: state}
          if (changed(data, newData)) {
            console.log('update')
            update({})// 强制刷新组件
          }
        })
      }, [selector]) // 强制刷新wrapper组件,每次dispatch就刷新dispatch的组件。
      const dispatch = (action) => {
        setState(reducer(appState, action))
      }
      return <Component {...props} {...data} dispatch = {dispatch}/>
    }
    return Wrapper
}
export const appContext = React.createContext(null)

app.js代码修改为:

import React from 'react';
import {connect, appContent, store} from './redux'
function App() {
  return (
    <appContext.Provider store={store}>
      <One/>
      <Two/>
      <Three/>
    </appContext.Provider>
  );
}

const User = connect(state => {
  return { user: state.user}
})(({dispatch, state}) => {
  return <div>User:{user.name}</div>
})
const UserModifie = connect()(({dispatch, state, children}) => {
   const onChange = (e) => {
     dispatch({type: 'updateUser', payLoad:{name: e.target.value}})
   }
   return (
    <div>
      {children}
      <input value={state.user.name} onChange={onChange}/>
    </div>
  )
})
const One = () => <section>one <User/></section>
const Two = () => <section>two<UserModifie>内容</UserModifie></section>
const Three = <section>three{group.name}</section>

mapDispatchertoProps

它是React-Redux里connect的第二个参数(dispatch选择器,又可以称为派发器),修改redux.js代码如下:

import React, {useState, useContext} from 'react';
const changed = (oldState, newState) => {
  let result = false
  Object.keys(newState).forEach(key => {
    if (newState[key] !== oldState[key]) {
      result = true
    }
  })
  Object.keys(oldState).forEach(key => {
    if (oldState[key] !== newState[key]) {
      result = true
    }
  })
  return result
}
export const store = {
  state: {
    user: {name: 'frank', age: 10},
  },
  setState(newState) {
    store.state = newState
    store.linsteners.map(fn => fn(store.state))
  },
  linsteners: [], // 用到connect的高阶组件,订阅更新,某个组件里面的state值更新了,遍历,其它组件也会更新
  subscribe(fn) {
      store.listeners.push(fn)
      return () => {
        const index = store.listeners.index(fn)
        store.listeners.splice(index, 1)
      }
  }
}
const reducer = (state, { type, payLoad }) => {
    if (type === 'updateUser') {
        return {
        ...state,
        user: {
            ...state.user,
            ...payLoad
        }
        }
    } else {
        return state
    }
}
export const connect = (selector, dispatchSelector) => (Component) => {
    const Wrapper = (props) => {
       const dispatch = (action) => {
        setState(reducer(appState, action))
      }
      const {state, setState} = useContext(appContext)
      const [, update] = useState({})
      const data = selector ? selector(state) : {state}
       const dispatchers = dispatchSelector ? dispatchSelector(dispatch) : {dispatch}
      useEffect(() => {
        store.subscribe(() => {
        const newData = selector ? selector(state) : {state: state}
          if (changed(data, newData)) {
            console.log('update')
            update({})// 强制刷新组件
          }
        })
      }, [selector]) // 强制刷新wrapper组件,每次dispatch就刷新dispatch的组件。
      return <Component {...props} {...data} {...dispatchers}/>
    }
    return Wrapper
}
export const appContext = React.createContext(null)

app.js修改:

import React from 'react';
import {connect, appContent, store} from './redux'
function App() {
  return (
    <appContext.Provider store={store}>
      <One/>
      <Two/>
      <Three/>
    </appContext.Provider>
  );
}

const User = connect(state => {
  return { user: state.user}
})(({dispatch, state}) => {
  return <div>User:{user.name}</div>
})
const UserModifie = connect(null, (dispatch) => {
   return {
    updateUser: (attrs) => dispatch({type: 'updateUser', payLoad: attrs})
  }
})(({updateUser, state, children}) => {
   const onChange = (e) => {
     updateUser({name: e.target.value})
   }
   return (
    <div>
      {children}
      <input value={state.user.name} onChange={onChange}/>
    </div>
  )
})
const One = () => <section>one <User/></section>
const Two = () => <section>two<UserModifie>内容</UserModifie></section>
const Three = <section>three{group.name}</section>

抽取公共connect代码

新建connedtToUser.js文件(提前公共读写工具),里面代码:

import {connect} from '../redux'
const userSelector = state => {
    return {user: state.user}
}
const userDispatcher = (dispatch) => {
  return {
    updateUser: (attrs) => dispatch({type: 'updateUser', payLoad: attrs})
  }
}
export const connectToUser = connect(userSelector, userDispatcher)

app.js代码修改如下:

import React from 'react';
import {connect, appContent, store} from './redux'
import {connectToUser} from './connedtToUser'
function App() {
  return (
    <appContext.Provider store={store}>
      <One/>
      <Two/>
      <Three/>
    </appContext.Provider>
  );
}

const User = connectToUser(({dispatch, state}) => {
  return <div>User:{user.name}</div>
})
const UserModifie = connectToUser(({updateUser, state, children}) => {
   const onChange = (e) => {
     updateUser({name: e.target.value})
   }
   return (
    <div>
      {children}
      <input value={state.user.name} onChange={onChange}/>
    </div>
  )
})
const One = () => <section>one <User/></section>
const Two = () => <section>two<UserModifie>内容</UserModifie></section>
const Three = <section>three{group.name}</section>

总结:connect(MapStateToProps, MapDispatchToProps),MapStateToProps封装读操作,MapDispatchToProps封装写操作,connect封装读写操作。

CreateStore、Provider

新建CreateStore(reducer,initState),创建Store,并新建react-redux里面的Provider组件。 修改redux.js,代码如下:

import React, {useState, useContext} from 'react';
const changed = (oldState, newState) => {
  let result = false
  Object.keys(newState).forEach(key => {
    if (newState[key] !== oldState[key]) {
      result = true
    }
  })
  Object.keys(oldState).forEach(key => {
    if (oldState[key] !== newState[key]) {
      result = true
    }
  })
  return result
}
const store = {
  state: null,
  setState(newState) {
    store.state = newState
    store.linsteners.map(fn => fn(store.state))
  },
  linsteners: [], // 用到connect的高阶组件,订阅更新,某个组件里面的state值更新了,遍历,其它组件也会更新
  subscribe(fn) {
      store.listeners.push(fn)
      return () => {
        const index = store.listeners.index(fn)
        store.listeners.splice(index, 1)
      }
  }
}
let reducer = null
export const createStore = (_reducer, initState) => {
  store.state = initState
  reducer = _reducer
  return store
}
export const connect = (selector, dispatchSelector) => (Component) => {
    const Wrapper = (props) => {
       const dispatch = (action) => {
        setState(reducer(appState, action))
      }
      const {state, setState} = useContext(appContext)
      const [, update] = useState({})
      const data = selector ? selector(state) : {state}
       const dispatchers = dispatchSelector ? dispatchSelector(dispatch) : {dispatch}
      useEffect(() => {
        store.subscribe(() => {
        const newData = selector ? selector(state) : {state: state}
          if (changed(data, newData)) {
            console.log('update')
            update({})// 强制刷新组件
          }
        })
      }, [selector]) // 强制刷新wrapper组件,每次dispatch就刷新dispatch的组件。
      return <Component {...props} {...data} {...dispatchers}/>
    }
    return Wrapper
}
export const appContext = React.createContext(null)
export const Provider = ({store, children}) => {
  return (
    <appContext.Provider value={store}>
      {children}
    </appContext.Provider>
  )
}

app.js代码修改如下:

import React from 'react';
import {connect, Provider, createStore} from './redux'
import {connectToUser} from './connedtToUser'
const state = {
  user: {name: 'frank', age: 10},
  group: {name: 'web前端'}
}
const reducer = (state, { type, payLoad }) => {
    if (type === 'updateUser') {
        return {
        ...state,
        user: {
            ...state.user,
            ...payLoad
        }
        }
    } else {
        return state
    }
}
const store = createStore(reducer, state)
function App() {
  return (
    <Provider store={store}>
      <One/>
      <Two/>
      <Three/>
    </Provider>
  );
}

const User = connectToUser(({dispatch, state}) => {
  return <div>User:{user.name}</div>
})
const UserModifie = connectToUser(({updateUser, state, children}) => {
   const onChange = (e) => {
     updateUser({name: e.target.value})
   }
   return (
    <div>
      {children}
      <input value={state.user.name} onChange={onChange}/>
    </div>
  )
})
const One = () => <section>one <User/></section>
const Two = () => <section>two<UserModifie>内容</UserModifie></section>
const Three = <section>three{group.name}</section>

现在基本完成redux的主要api,已经基本理解redux的思想了。

redux.js里API重构

官方的api和现在的api还是不同,重构代码redeux.js如下:

import React, {useState, useEffect} from 'react';
let state = undefined
let reducer = undefined
let listeners = []
const setState = (newState) => {
  state = newState
  listeners.forEach(fn => fn(state))
}
const store = {
    getState() {
      return state
    },
    dispatch: (action) => {
      setState(reducer(state, action))
    },
    subscribe: (fn) => {
      listeners.push(fn)
      return () => {
        const index = listeners.index(fn)
        listeners.splice(index, 1)
      }
    },
    replaceReducer(newReducer) {
      reducer = newReducer 
    }
}
export const createStore = (_reducer, initState) => {
  state = initState
  reducer = _reducer
  return store
}
const changed = (oldState, newState) => {
  let result = false
  Object.keys(newState).forEach(key => {
    if (newState[key] !== oldState[key]) {
      result = true
    }
  })
  Object.keys(oldState).forEach(key => {
    if (oldState[key] !== newState[key]) {
      result = true
    }
  })
  return result
}
const  dispatch = store.dispatch
export const connect = (selector, dispatchSelector) => (Component) => {
    const Wrapper = (props) => {  
      const [, update] = useState({})
      const data = selector ? selector(state) : {state}
      const dispatchers = dispatchSelector ? dispatchSelector(dispatch) : {dispatch}
      useEffect(() => 
        store.subscribe(() => {
          const newData = selector ? selector(state) : {state: state}
          if (changed(data, newData)) {
            console.log('update')
            update({})
          }
        }), // 强制刷新wrapper组件,每次dispatch就刷新dispatch的组件。
        [selector])
      return <Component {...props} {...data} {...dispatchers}/>
    }
    return Wrapper
}
const appContext = React.createContext(null)

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

中间件

重写dispatch,以前dispatch参数只能是对象,现在要支持异步函数:redux代码修改如下:

import React, {useState, useEffect} from 'react';
let state = undefined
let reducer = undefined
let listeners = []
const setState = (newState) => {
  state = newState
  listeners.forEach(fn => fn(state))
}
const store = {
    getState() {
      return state
    },
    dispatch: (action) => {
      setState(reducer(state, action))
    },
    subscribe: (fn) => {
      listeners.push(fn)
      return () => {
        const index = listeners.index(fn)
        listeners.splice(index, 1)
      }
    }
}
export const createStore = (_reducer, initState) => {
  state = initState
  reducer = _reducer
  return store
}
const changed = (oldState, newState) => {
  let result = false
  Object.keys(newState).forEach(key => {
    if (newState[key] !== oldState[key]) {
      result = true
    }
  })
  Object.keys(oldState).forEach(key => {
    if (oldState[key] !== newState[key]) {
      result = true
    }
  })
  return result
}
let  dispatch = store.dispatch
// 重写dispatch,支持异步anction,如react-thunk等
const prevDispatch = dispatch
dispatch = (action) => {
  if (action instanceof Function) {
    action(dispatch) // dispatch既可以处理函数,又可以处理对象
  } else {
    prevDispatch(action)
  }
}
const prevDispatch2 = prevDispatch
dispatch = (action) => {
  if (action.payLoad instanceof Promise) {
    action.payLoad.then(data => {
      dispatch({...action, payLoad: data}) // data还是dispatch
    })
  } else {
    prevDispatch2(action)
  }
}
// 重写重写dispatch完
export const connect = (selector, dispatchSelector) => (Component) => {
    const Wrapper = (props) => {
      // const {state, setState} = useContext(appContext)
      // const {setState} = useContext(appContext)
  
      const [, update] = useState({})
      const data = selector ? selector(state) : {state}
      const dispatchers = dispatchSelector ? dispatchSelector(dispatch) : {dispatch}
      useEffect(() => 
        store.subscribe(() => {
          const newData = selector ? selector(state) : {state: state}
          if (changed(data, newData)) {
            console.log('update')
            update({})
          }
        }), // 强制刷新wrapper组件,每次dispatch就刷新dispatch的组件。
        [selector])
      return <Component {...props} {...data} {...dispatchers}/>
    }
    return Wrapper
}
const appContext = React.createContext(null)

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