redux1 - 20行代码实现redux

手写实现redux,react-redux的api,中间件原理

redux源码参考
准备工作

首先建一个React的脚手架项目, 删除src下的所有东西,保留一个空的 index.js

当前目录
index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

src/ index.js

// 空 的js

需求1:js 实现渲染(风影嘎拉)三个字到 div 容器中

let state = {
  name: '风影嘎拉',
  color: '#f00',
}
function render(obj) {
  const dom = document.getElementById('root')
  dom.innerHTML = obj.name
  dom.style.color = obj.color
}
render(state)

启动服务:
在这里插入图片描述

当前的设计呢,存在诸多问题, 比如 state 变量谁都可以更改,不安全, 而每次更改时,不同的人可能会更改不同的值,
那么,是不是可以通过派发动作的方式,来改变具体的属性, 比如只改变名字, 只改变颜色, 接着下边改进

  1. 改成通过派发一个动作,让状态改变,一个动作干一件事
let state = {
  name: '风影嘎拉',
  color: '#f00',
}

function render(obj) {
  const dom = document.getElementById('root')
  dom.innerHTML = obj.name
  dom.style.color = obj.color
}

// 添加动态改写逻辑
const CHANGE_NAME = 'CHANGE_NAME'
const CHANGE_COLOR = 'CHANGE_COLOR'
function dispatch(action) {
  switch (action.type) {
    case CHANGE_NAME:
      state.name = '手鞠'
      break
    case CHANGE_COLOR:
      state.color = action.color
      break
    default:
      break
  }
}
// 派发动作改变名字
dispatch({ type: CHANGE_NAME })
//派发动作改变颜色, 并传参
dispatch({ type: CHANGE_COLOR, color: 'green' })
render(state)

在这里插入图片描述
虽然改进了,一个动作做一件事, 但还是那个问题,全局的state 变量可以随意更改,
比如说,我可以手动把state 的值置位 null, 这显然是不合理的, 我希望变量能私有化

变量私有化, 可以采用 IIFE 函数(明显此处不合适), 也可以采用函数闭包的形势

创建一个 createStore 方法, 用来创建数据源

function render(obj) {
  const dom = document.getElementById('root')
  dom.innerHTML = obj.name
  dom.style.color = obj.color
}

// 添加动态改写逻辑
const CHANGE_NAME = 'CHANGE_NAME'

/**
 *
 * 进行变量私有化
 * 函数,闭包
 */
function createStore() {
  let store = {
    name: '风影嘎啦',
    color: '#f26',
  }
  // 获取状态
  function getState() {
    return store 
  }
  // 派发动作改变数据源
  function dispatch(action) {
    switch (action.type) {
      case CHANGE_NAME:
        store.name = '手鞠'
        break
      default:
        break
    }
  }
  // 导出
  return {
    getState,
    dispatch,
  }
}
// 创建 store 数据源
let store = createStore()

// 派发 action 改变名字 
store.dispatch({
  type: CHANGE_NAME,
})
// 调用 store.getState() 获取数据源
render(store.getState())

在这里插入图片描述
到这里,前边的问题算是解决了, 但是又有一个新问题了,状态外部无法改变,内部直接写死了,简单画个草图
在这里插入图片描述
此时,A, B, C 三个组件,可能每个组件都有自己的特有的数据源,但此时的数据源可能只适合 A 组件
所以,截下来要解决的问题就是,能动态的数据源, 固定的输入,得到固定的输出

  1. 在 createStore() 方法时,传入一个纯函数
  2. 解决数据状态由用户自己具体指定
  3. 固定的输入,得到固定的输出
function render(obj) {
  const dom = document.getElementById('root')
  dom.innerHTML = obj.name
  dom.style.color = obj.color
}

/**
 *
 * 进行变量私有化
 * 函数,闭包
 */

function createStore(reducer) {
  // 默认初始状态
  let state
  // 得到总的状态树
  function getState() {
    return state
  }
  // 派发动作
  function dispatch(action) {
    // 得到新状态
    state = reducer(state, action)
  }
  // 由于默认 state 树是没有状态的, 是一个控制,所以先派发一次action,给初始状态赋值,
  // 由于用户自己定义的reducer 默认返回初始状态,由于派发的这个type类型找不到,所以默认返回了初始值
  dispatch({ type: '@@REDUX_INIT' })
  return {
    getState,
    dispatch,
  }
}

// 初始状态
let initState = {
  name: '我爱罗',
  color: '#f26',
}

// 添加动态改写逻辑
const CHANGE_NAME = 'CHANGE_NAME'
// 状态用户自己定义,传入老的状态,返回新状态
function reducer(state = initState, action) {
  switch (action.type) {
    case CHANGE_NAME:
      return {
        ...state,
        name: action.name,
      }
    default:
      return state
  }
}
// 得到数据源
let store = createStore(reducer)
// 派发action动作
store.dispatch({
  type: CHANGE_NAME,
  name: '7代目止水',
})

render(store.getState())

在这里插入图片描述
此时,基本实现了简化版redux, 然而还有一个问题, 就是如果你先渲染,然后再派发动作的话, 视图是不会改变的,你只能先派发action , 才会立马更新视图

let store = createStore(reducer)
// 如果在这不会视图更新
render(store.getState())
store.dispatch({
  type: CHANGE_NAME,
  name: '手鞠',
})
// 如果先派发动作,视图会更新
// render(store.getState())

那如果我就想先渲染更新,接着再渲染后,继续提交action,还要引起视图的更新,就像上边代码一样,先渲染,再render

此时可以使用发布订阅模式,来监听你的改变
一个简单的发布订阅模式:
在这里插入图片描述
接着实现发布订阅,那既然可以订阅, 就可以取消订阅

function render(obj) {
  const dom = document.getElementById('root')
  dom.innerHTML = obj.name
  dom.style.color = obj.color
}

/**
 *
 * 进行变量私有化
 * 函数,闭包
 */

function createStore(reducer) {
  // 默认初始状态
  let state
  let listeners = [] // 存储所有的监听函数
  // 得到总的状态树
  function getState() {
    return state
  }
  // 派发动作
  function dispatch(action) {
    // 得到新状态
    state = reducer(state, action)
    // 订阅
    listeners.forEach((fn) => fn())
  }
  // 由于默认 state 树是没有状态的, 是一个控制,所以先派发一次action,给初始状态赋值,
  // 由于用户自己定义的reducer 默认返回初始状态,由于派发的这个type类型找不到,所以默认返回了初始值
  dispatch({ type: '@@REDUX_INIT' })
  // 添加发布订阅模式
  function subscribe(listener) {
    listeners.push(listener)
    // 返回一个取消订阅方法(就是个高阶函数)
    return function () {
      listeners = listeners.filter((fn) => fn !== listener)
    }
  }
  return {
    getState,
    dispatch,
    subscribe,
  }
}
let app = {
  name: '我爱罗',
  color: '#f26',
}

// 添加动态改写逻辑
const CHANGE_NAME = 'CHANGE_NAME'
const CHANGE_COLOR = 'CHANGE_COLOR'

function reducer(state = app, action) {
  switch (action.type) {
    case CHANGE_NAME:
      return {
        ...state,
        name: action.name,
      }
    case CHANGE_COLOR:
      return {
        ...state,
        color: action.color,
      }
    default:
      return state
  }
}

let store = createStore(reducer)

// 必选先派发action , 再去触发渲染,不能先渲染,再派发动作
/**
 * 解决办法: 添加发布订阅模式
 */
const AppRender = () => render(store.getState())

// 先调用渲染一次
AppRender()

let unSubscribe = store.subscribe(AppRender)

// 模拟延时渲染
setTimeout(() => {
  store.dispatch({
    type: CHANGE_NAME,
    name: '手鞠',
  })
  // 取消订阅, 下边的不会在执行了
  // unSubscribe()
  store.dispatch({
    type: CHANGE_COLOR,
    color: 'blue',
  })
}, 1500)

最后总结代码也就20多行吧,暴露出3个api

function createStore(reducer) {
  let state
  let listeners = []
  function getState() {
    return state
  }
  function dispatch(action) {
    state = reducer(state, action)
    listeners.forEach((fn) => fn())
  }
  dispatch({ type: '@@REDUX_INIT' })
  function subscribe(listener) {
    listeners.push(listener)
    return function () {
      listeners = listeners.filter((fn) => fn !== listener)
    }
  }
  return {
    getState,
    dispatch,
    subscribe,
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章