熟悉React
的开发者并不陌生Redux
是什么,一个状态管理容器,简单来说就是帮助前端存储交互的数据的容器。
笔者今天聊的就是如何手写一个Redux
,破除那些把Redux
神话的同学么。
Tip: 一定要熟悉JavaScript的原理哦
了解Redux的运行原理
三大原则
- 单一数据流
- State只读
- 只能使用纯函数来执行修改
工作原理
从图片中我们可以看到,Redux的工作原理Action Creators
通过派发方法dispatch
,然后派发的action
会被Reducer
层拿到进行处理,最后行成新的Store
数据,最后被React Component绑定
, 页面发生改变
声明Store
我们需要创建一个Store的函数
function createStore(reducer){
let state;
let getState = () => JSON.parse(JSON.stringify(state))
return {
getState,
}
}
复制代码
这里的reducer
是一个回调函数
const CHANGE_TITLE = "change_title"
function reducer(state={title: "标题"}, action){
switch(action.type){
// 一下的reducer就是我们具体实现存储值的逻辑
case CHANGE_TITLE:
return {...state, title: action.data}
default:
break;
}
return state
}
复制代码
看到这里是不是有那味了,我想对于React+Redux的使用者来说这个reducer是不是非常熟悉了,那么每一次的派发我们只需要重新reducer一次,就可以把派发的值给重新修改了
diapatch方法
function dispatch(action){
state = reducer(state, action)
}
复制代码
ok
派发方法有了,创建Store函数有了,我们还差render方法,来让页面同步更新
创建render方法我们必须明白,当state发生改变的时候,页面就应该响应了,那么这里不得不使用一个订阅发布者模式来完成
创建render方法并优化dispatch方法
function createStore(reducer){
let state;
function dispatch(action){
state = reducer(state, action)
// 每一个改变值之后去重新渲染监听器中的函数
listeners.forEach(item => item())
}
// 优化思路 加入发布订阅模 式
let listeners = []
let subscribe = (fn) => {
listeners.push(fn)
// 加入取消绑定的函数
return () => {
listeners => listeners.filter(item > item!=fn)
}
}
// 第一次创建需要覆盖自身的对象
dispatch({})
let getState = () => JSON.parse(JSON.stringify(state))
return {
getState,
dispatch,
subscribe
}
}
let CHANGE_TITLE = "change_title"
let store = createStore(reducer)
function reducer(state={title: "标题"}, action){
switch(action.type){
// 一下的reducer就是我们具体实现存储值的逻辑
case CHANGE_TITLE:
return {...state, title: action.data}
default:
break;
}
return state
}
// 定义一个渲染页面节点的函数
function render(){
document.querySelector(".title").innerHTML = store.getState().title
}
复制代码
这里我们可以看到,render方法看似比较简单的,但是如果让render方法可以比较聪明的渲染呢?
他们就是关键。首先subscribe
是一个订阅函数,用来传入每一个render
,然后将每一个render
函数放入监听数组中,等待下一次dispatch
改变值的时候来循环调用数组中的render
函数,这样就形成了,数据已发生改变就可以立即响应页面的功能了,我们来看看完整的代码是什么样子的
/**
* 核心概念store state reducer dispatch action
*/
function createStore(reducer){
let state;
function dispatch(action){
state = reducer(state, action)
// 每一个改变值之后去重新渲染监听器中的函数
listeners.forEach(item => item())
}
// 优化思路 加入发布订阅模 式
let listeners = []
let subscribe = (fn) => {
listeners.push(fn)
// 加入取消绑定的函数
return () => {
listeners => listeners.filter(item >= item!=fn)
}
}
// 第一次创建需要覆盖自身的对象
dispatch({})
let getState = () => JSON.parse(JSON.stringify(state))
return {
getState,
dispatch,
subscribe
}
}
let CHANGE_TITLE = "change_title"
let store = createStore(reducer)
function reducer(state={title: "标题"}, action){
switch(action.type){
// 一下的reducer就是我们具体实现存储值的逻辑
case CHANGE_TITLE:
return {...state, title: action.data}
default:
break;
}
return state
}
// 定义一个渲染页面节点的函数
function render(){
document.querySelector(".title").innerHTML = store.getState().title
}
// 第一次页面加载的render方法
render()
// 拥有监听器之后我们只需要
let unsubscribe = store.subscribe(render)
// 每次更新值的render方法
setInterval(() => {
store.dispatch({
type: CHANGE_TITLE,
data: "我是重新改变之后的title"+ Math.random() * 100
})
// 上面渲染完成之后直接将没有用的监听函数取消
unsubscribe()
}, 2000)
// 大致上面实现思路就是一个最简单的redux的实现过程
复制代码
大家可以把它放在一个页面之中运行就可以看到效果了,本文的环境基于node v14
,就不做效果演示了.???