跟着B站手写redux

发布时间 2023-05-09 15:08:33作者: 时间观测者

来,跟我一起手写 Redux!(建议 2 倍速播放)_哔哩哔哩_bilibili

https://www.bilibili.com/video/BV1dm4y1R7RK/?spm_id_from=333.788&vd_source=fdb6783d7930065bbf3d29c851463887

 

//src目录结构

│ App.jsx
│ index.jsx
│ redux.jsx
│ style.css
│
└─connecters
connectToUser.js

/*----------------------------------*/

//package.json

{
  "name": "redux-001",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "serve": "vite preview"
  },
  "dependencies": {
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "devDependencies": {
    "@vitejs/plugin-react-refresh": "1.3.1",
    "vite": "2.1.2"
  }
}

/*--------------------------*/
// README.md
# 使用说明

本项目是采用 [Vite](https://github.com/vitejs/vite#vite-) 搭建的,开发时的编译速度超快!

## 开发

运行 `yarn dev` 或者 `npm run dev` 即可开始开发

## 打包

运行 `yarn build` 或者 `npm run build` 即可打包文件

  

// App.jsx
//
请从课程简介里下载本代码 import React from 'react' import {Provider, createStore, connect} from './redux.jsx' import {connectToUser} from './connecters/connectToUser' // 把reducer从redux里搬出来放在app定义 const reducer = (state, {type, payload}) => { // 给dispatch重新封装了一个函数updateUser if (type === 'updateUser') { return { ...state, user: { ...state.user, ...payload } } } else { return state } } // 定义一个state初值 const initState = { user: {name: 'frank', age: 18}, group: {name: '前端组'} } // 使用store创建初值 const store = createStore(reducer, initState) // 使用封装过的Provider export const App = () => { return ( <Provider store={store}> <大儿子/> <二儿子/> <幺儿子/> </Provider> ) } const 大儿子 = () => { console.log('大儿子执行了 ' + Math.random()) return <section>大儿子<User/></section> } const 二儿子 = () => { console.log('二儿子执行了 ' + Math.random()) return <section>二儿子<UserModifier/></section> } const 幺儿子 = connect(state => { return {group: state.group} })(({group}) => { console.log('幺儿子执行了 ' + Math.random()) return <section>幺儿子 <div>Group: {group.name}</div> </section> }) // 这里的函数是connect接受的形参component组件,实参呢又是在connect函数组件里给component里传的props const User = connectToUser(({user}) => { console.log('User执行了 ' + Math.random()) return <div>User:{user.name}</div> }) // 模拟一个请求 const ajax = () => { return new Promise((resolve, reject) => { setTimeout(() => { resolve({data: {name: '3秒后的frank'}}) }, 3000) }) } const fetchUserPromise = () => { return ajax('/user').then(response => response.data) } // 增加一个函数用于异步的dispatch // 支持异步,塞入的dispatch可以在then之后调用 // 增加一个函数用于异步的dispatch const fetchUser = (dispatch) => { return ajax('/user').then(response => dispatch({type: 'updateUser', payload: response.data})) } const UserModifier = connect(null, null)(({state, dispatch}) => { console.log('UserModifier执行了 ' + Math.random()) const onClick = (e) => { // 两种调用方式 //第一种繁琐,但是参数清晰 dispatch({type: 'updateUser', payload: fetchUserPromise()}) // 第二种简洁,但是要创建函数 // dispatch(fetchUser) } return <div> <div>User: {state.user.name}</div> <button onClick={onClick}>异步获取 user</button> </div> })

 

// redux.jsx
import React, {useContext, useEffect, useState} from 'react'
// 初值定义为空
// 把store里的状态放到外面,防止被访问后更改
let state = undefined
let reducer = undefined
// 监听用函数队列
let listeners = []
const setState = (newState) => {
  state = newState
  listeners.map(fn => fn(state))
}
const store = {
  getState(){
    // 使用函数获取但是不让修改
    return state
  },
  // 用reducer函数做唯一的修改方式,规范操作,防止无法追根溯源。
  dispatch: (action) => {
    setState(reducer(state, action))
  },
  // 发布用函数
  subscribe(fn) {
    //每次发布就往队列里推入一个函数
    // 把刷新页面的函数推给订阅函数
    listeners.push(fn)
    return () => {
      const index = listeners.indexOf(fn)
      listeners.splice(index, 1)
    }
  }
}
let dispatch = store.dispatch

const prevDispatch = dispatch

dispatch = (action)=>{
  // 如果是函数就把dispatch做参数传给action
  if(action instanceof Function){
    action(dispatch)
  } else{
    // 如果是对象就把action作为参数传给prevDispatch,就是和之前的dispatch用法一致
    prevDispatch(action) // 对象 type payload
  }
}

// 用于承载promise的变量
const prevDispatch2 = dispatch
// 如果是promise类型的函数,.then内回调dispatch
dispatch = (action) => {
  if(action.payload instanceof Promise){
    action.payload.then(data=> {
      dispatch({...action, payload: data})
    })
  }else{
    // 不然直接运行
    prevDispatch2(action)
  }
}

// 定义createStore来获取初值和用来修改初值的reducer函数
export const createStore = (_reducer, initState) => {
  // 直接闭包修改
  state = initState
  reducer = _reducer
  return store
}
// 定义一个变量用于确定值是否修改
const changed = (oldState, newState) => {
  let changed = false
  // 遍历两个对象,确认每个key对应的value值是否一致
  for (let key in oldState) {
    if (oldState[key] !== newState[key]) {
      //  然后修改作为是否改变的值
      changed = true
    }
  }
  return changed
}
// connect是个高阶组件,接受一个组件,处理后输出一个组件
// connect(MapStateToProps读,MapDispatchToProps写)(组件)
// connect封装读和写的资源
// connect是模拟react的第二个库react-redux提供的
// 选传数据selector函数
export const connect = (selector, dispatchSelector) => (Component) => {
  // 因为返回一个组件,所以取名为wrapper封皮
  // 这里的props就和创建函数组件一样是被动传入props的
  const Wrapper = (props) => {
// 做一个判断,selector存在或者不存在时用的
    // 封装读接口
    const data = selector ? selector(state) : {state}
    // 封装写接口
    const dispatchers = dispatchSelector ? dispatchSelector(dispatch) : {dispatch}
    // useState的设置部分给update
    // 函数update({})参数是新对象,所以百分百会更新
    const [, update] = useState({})
    // 订阅store更新
    useEffect(() => store.subscribe(() => {
      // selector数据不存在时用老数据,存在时用
      const newData = selector ? selector(state) : {state}
      // 如果更新了
      if (changed(data, newData)) {
        //刷新
        update({})
      }
    }), [selector])
    // 渲染组件
//  给传入的组件函数,写上参数
    return <Component {...props} {...data} {...dispatchers}/>
  }
  return Wrapper
}

export const appContext = React.createContext(null)

// 这里替代了之前的useContext,然后将appContext作用域组件赋值为Provider
export const Provider = ({store, children}) => {
  return (
    <appContext.Provider value={store}>
      {children}
    </appContext.Provider>
  )
}