20行左右的代码一步步的实现简化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 变量谁都可以更改,不安全, 而每次更改时,不同的人可能会更改不同的值,
那么,是不是可以通过派发动作的方式,来改变具体的属性, 比如只改变名字, 只改变颜色, 接着下边改进
- 改成通过派发一个动作,让状态改变,一个动作干一件事
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 组件
所以,截下来要解决的问题就是,能动态的数据源, 固定的输入,得到固定的输出
- 在 createStore() 方法时,传入一个纯函数
- 解决数据状态由用户自己具体指定
- 固定的输入,得到固定的输出
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,
}
}
上一篇: 梯度下降优化算法