手写redux

手写redux

      • 一、结构图
      • 二、代码实现
        • 2.1.redux.js
        • 2.2.connect.js
        • 2.3.SonScreen.jsx
        • 2.4.user.json
      • 三、前端展示

一、结构图

手写redux_第1张图片

参考视频

二、代码实现

2.1.redux.js
import React, { useContext, useEffect, useState } from 'react'

// 创建全局上下文
const Context = React.createContext(null)
// 封装上下文组件
export const Provider = ({ store, children }) => {
  return <Context.Provider value={store}>{children}</Context.Provider>
}

// 解决多次render,将store和setState提取出来,订阅变化:设置订阅及监听队列
let state = undefined
let reducer = undefined
let listeners = []
const setState = (newState) => {
  state = newState
  listeners.map((fn) => fn(state))
}
const store = {
  // state: { user: { name: 'frank', age: 18 }, group: { name: '前端组' } },
  // 将state隐藏,使用getState获得
  getState: () => {
    return state
  },
  // 使用dispatch规范setState的流程
  dispatch: (action) => {
    setState(reducer(state, action))
  },
  subscribe: (fn) => {
    listeners.push(fn)
    return () => {
      const index = listeners.indexOf(fn)
      listeners.splice(index, 1)
    }
  }
}

// 使redux支持 异步action【函数和Promise】
// 中间件:MiddleWare【可以自己去修饰dispatch】
// 怎么通过中间件让redux支持异步Action:redux-thunk【发现action是个函数就调用它,否则就进入下一个中间件】,redux-promise【发现action.payload是一个Promise,就在Promise后面接上一个then,并将data直接传给payload】
let dispatch = store.dispatch
const preDispatch = dispatch
dispatch = (action) => {
  if (action instanceof Function) {
    action(dispatch) // 传入dispatch进行递归,既可以处理函数,又可以处理对象,因为这里的action是一个函数,可能会再传一个fn回来
  } else {
    preDispatch(action) // 可以确定这里的action包含一个type和一个payload
  }
}
const preDispatch2 = dispatch
dispatch = (action) => {
  if (action.payload instanceof Promise) {
    action.payload.then((data) => {
      dispatch({ ...action, payload: data }) // 以防再次传入Promise,使用递归
    })
  } else {
    preDispatch2(action)
  }
}

// createStore作用:创建store
// state和redux不能是redux内部自定义的,需要外部传入
export const createStore = (_reducer, initState) => {
  state = initState
  reducer = _reducer
  return store
}

// 判断新旧数据是否相等
const changed = (oldData, newData) => {
  let changed = false
  for (let key in oldData) {
    if (oldData[key] !== newData[key]) {
      changed = true
    }
  }
  return changed
}

// connect来规范创建中间件:让组件与store全局状态连接起来,由react-redux提供
// (selector,dispatchSelector)=>(Component)=> 表示先接收一个参数,再接收另一个参数
// selector是一个函数,为了实现返回组件需要的state数据,比如只使用user,就返回user:state.user
// dispatchSelector是一个函数,为了实现返回需要的dispatch,比如只修改user,就返回一个userDispatch
// Component是一个组件,实现高阶组件,一个函数接收一个组件,返回一个新的组件
export const connect = (selector, dispatchSelector) => (Component) => {
  // React-Redux提供的selector
  return (props) => {
    const [, update] = useState({})
    // 得到具体的data:如果用户传入了selector,就传入局部的state,否则就传入全局的state
    const data = selector ? selector(state) : { state }
    // 得到具体的dispatch:如果传入了dispatchSelector,就传入局部的dispatchSelector,否则传入全局的dispatch
    const dispatchers = dispatchSelector ? dispatchSelector(dispatch) : { dispatch }
    // 初次渲染对store进行订阅,如果store值发生变化,在data数据变更的情况下调用update进行相应组件的重新渲染
    useEffect(() => {
      // store.subscribe 返回一个unsubscribe取消订阅的函数,最好取消订阅,因为不知道selector会不会变化出现重复订阅的情况
      return store.subscribe(() => {
        // 得到订阅后新的data
        const newData = selector ? selector(state) : { state }
        // 实现精准渲染,组件只在自己的数据变化时render,判断旧data与新data是否一样,如果不一样才修改,这样如果修改state中的user值用到user的组件会重新渲染,但用到group的组件是不会重新渲染的
        if (changed(data, newData)) {
          update({})
        }
      })
    }, [selector])
    return <Component {...props} {...data} {...dispatchers} />
  }
}
2.2.connect.js
import { connect } from './redux'
// 抽取公共selector和dispatch
const userSelector = (state) => {
  return { user: state.user } // 只使用了state中的user,redux.js中的data就获取的是user的值
}
const groupSelector = (state) => {
  return { group: state.group } // 只使用了state中的group,redux.js中的data就获取的是group的值
}
const userDispatch = (dispatch) => {
  return { userDispatch: (attrs) => dispatch({ type: 'updateUser', payload: attrs }) }
}
const groupDispatch = (dispatch) => {
  return { groupDispatch: (attrs) => dispatch({ type: 'updateUser', payload: attrs }) }
}

// 将selector与dispatch合并
// connect(MapStateToProps,MapDispatchToProps)(组件),MapStateToProps为了让组件可以快速获取一个局部的state,MapDispatchToProps为了让组件可以快速获取一个局部的dispatch
export const connectToUser = connect(userSelector, userDispatch)
export const connectToGroup = connect(groupSelector, groupDispatch)
2.3.SonScreen.jsx
import React from 'react'
import { Provider, createStore, connect } from './utils/redux'
import { connectToUser, connectToGroup } from './utils/connect'

const initialState = { user: { name: 'frank', age: 18 }, group: { name: '前端组' } }
// 规范创建state的过程,每次更新state时,不能使用原来的state,要创建一个新的state
const reducer = (state, action) => {
  const { type, payload } = action
  if (type === 'updateUser') {
    return {
      ...state,
      user: {
        ...state.user,
        ...payload
      }
    }
  } else {
    return state
  }
}
// 创建store,传入一个reducer【规范state创建流程】,一个initalState【state的初始值】
const store = createStore(reducer, initialState)

export const SonScreen = () => {
  return (
    <Provider store={store}>
      <OneSon />
      <TwoSon />
      <ThreeSon />
    </Provider>
  )
}

const OneSon = () => {
  return (
    <section style={{ color: 'black', border: '1px solid black', marginBottom: '20px' }}>
      大儿子7
      <User />
    </section>
  )
}

const TwoSon = () => {
  return (
    <section style={{ color: 'black', border: '1px solid black', marginBottom: '20px', padding: '20px' }}>
      二儿子7
      <UserModifier2>内容</UserModifier2>
    </section>
  )
}

const ThreeSon = connectToGroup(({ group }) => {
  return <section style={{ color: 'black', border: '1px solid black' }}>三儿子7 {group.name}</section>
})

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

const UserModifier = connectToUser(({ userDispatch, user, children }) => {
  const onChange = (e) => {
    userDispatch({ name: e.target.value })
  }
  return (
    <div>
      {children}
      <input value={user.name} onChange={onChange} />
    </div>
  )
})

// fetch获取public/user.json下面的数据
const fetchFun = async (url) => {
  try {
    const response = await fetch(process.env.PUBLIC_URL + url)
    return response.json() // {name:'康康',age:20}
  } catch (error) {
    console.error(error)
  }
  return null
}
const fetchUser = (dispatch) => {
  fetchFun('/user.json').then((user) => {
    dispatch({ type: 'updateUser', payload: user })
  })
}
const UserModifier2 = connect(
  null,
  null
)(({ state, dispatch }) => {
  return (
    <div>
      <div>User:{state.user.name}</div>
      <button
        onClick={(e) => {
          // 实现异步函数类型的action:为了不写出dispatch(fetchUser)这样的代码
          // dispatch(fetchUser)
          // 实现异步Promise类型的action,fetchFun('/user.json')返回的是一个Promise
          dispatch({ type: 'updateUser', payload: fetchFun('/user.json') })
        }}
      >
        异步获取user
      </button>
    </div>
  )
})
2.4.user.json

如果你使用的是create-react-app 框架创建的,json文件要放在public目录下

{ "name": "康康", "age": 20 }

三、前端展示

手写redux_第2张图片

你可能感兴趣的:(Web前端,redux)