欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

20行左右的代码一步步的实现简化redux

程序员文章站 2022-07-04 22:03:00
...

仓库
准备工作

首先建一个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)

启动服务:
20行左右的代码一步步的实现简化redux

当前的设计呢,存在诸多问题, 比如 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)

20行左右的代码一步步的实现简化redux
虽然改进了,一个动作做一件事, 但还是那个问题,全局的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())

20行左右的代码一步步的实现简化redux
到这里,前边的问题算是解决了, 但是又有一个新问题了,状态外部无法改变,内部直接写死了,简单画个草图
20行左右的代码一步步的实现简化redux
此时,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())

20行左右的代码一步步的实现简化redux
此时,基本实现了简化版redux, 然而还有一个问题, 就是如果你先渲染,然后再派发动作的话, 视图是不会改变的,你只能先派发action , 才会立马更新视图

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

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

此时可以使用发布订阅模式,来监听你的改变
一个简单的发布订阅模式:
20行左右的代码一步步的实现简化redux
接着实现发布订阅,那既然可以订阅, 就可以取消订阅

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,
  }
}