共计 4756 个字符,预计需要花费 12 分钟才能阅读完成。
redux 只是一个状态管理
简述
由于项目原因,笔者接触了一些前端框架,其中 react,wepy 等都可以使用 redux 管理状态不多说,本篇文章主要从 react 和 wepy 中说明 redux 的原理以及如何使用
阅读前需了解(本文中仅作简单描述,详细了解请自行 Google)
纯函数 符合以下两点性质的函数即为纯函数
函数执行不改变外部变量
函数的输出结果仅依赖于输入参数
高阶组件
即一个函数,接收一个组件作为参数,输出一个新组件
观察者模式
即发布订阅模式,可以理解为给一个事件绑定多个函数,事件触发时多个绑定函数全部执行
react 基础
react 官方中文文档
wepy 基础(有 vue 基础也可以)
wepy 官方文档
redux
react 中的 context 属性
简单的说,context 就是一个全局变量,它可以被一个高阶组件及该高阶组件的所有子组件,孙组建等等共享
举个例子,下图是一个 react 页面
react 页面树形结构
正常的状态提升及数据下放
使用 context 的树形结构
由上面三图可以看出,本应一层一层传递的数据,在使用 context 后,变得方便了。高阶组件以下的所有子组件都可以直接从 context 中获取数据。
context 并不完美
这看似方便的方法,实际上引发了一个老生常谈的问题,即全局变量控制问题 context 里面的数据能被随意接触就能被随意修改,每个组件都能够改 context 里面的内容会导致程序的运行不可预料同时 context 的出现也打破了组件和组件之间通过 props 传递数据的规范,极大地增强了组件之间的耦合性。试想,若是所有组件都可以通过 xxx=’xxx’ 来修改状态,我们获取并控制当前状态的难度是否变大?在大型复杂项目中,我们可能都无法确定某数据是如何变成当前值的为了避免这种情况出现,redux 就出现了
redux 解决了问题
为了解决模块(组件)之间需要共享数据和数据可能被任意修改导致不可预料的结果时间的矛盾,redux 团队想出了一个办法,即把事情搞复杂一些,提高数据修改的门槛:模块(组件)之间可以共享数据,也可以改数据。但是我们约定,这个数据并不能直接改,你只能执行某些我允许的某些修改,而且你修改的必须大张旗鼓地告诉我。
所以,修改数据的函数 dispatch 出现了
function dispatch (action) {
switch (action.type) {
case ‘UPDATE_TITLE_TEXT’:
appState.title.text = action.text
break
case ‘UPDATE_TITLE_COLOR’:
appState.title.color = action.color
break
default:
break
}
}
即所有的数据都必须通过调用 dispatch 修改。
dispatch({type: ‘UPDATE_TITLE_TEXT’, text: ‘hello world’}) // 修改标题文本
dispatch({type: ‘UPDATE_TITLE_COLOR’, color: ‘blue’}) // 修改标题颜色
如图所示
引入 redux 前,各组件直接修改数据
现在,必须通过 dispatch 修改数据
抽离 store 并监控数据变化
我们把它们集中到一个地方,给这个地方起个名字叫做 store,然后构建一个函数 createStore,用来专门生产这种 state 和 dispatch 的集合,这样别的 App 也可以用这种模式了:
/**
*@param
*state 初始状态
*stateChanger 一个修改 state 的函数
*/
function createStore (state, stateChanger) {
const getState = () => state
const dispatch = (action) => stateChanger(state, action)
return {getState, dispatch}
}
本例中页面通过 renderApp 等函数刷新,为了避免 dispatch 后页面数据不变化 (render 函数不执行) 我们必须引入观察者模式,使 dispatch 后,app 自动执行 render 函数
function createStore (state, stateChanger) {
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
stateChanger(state, action)
listeners.forEach((listener) => listener())
}
return {getState, dispatch, subscribe}
}
function renderApp (appState) {
renderTitle(appState.title)
renderContent(appState.content)
}
function renderTitle (title) {
const titleDOM = document.getElementById(‘title’)
titleDOM.innerHTML = title.text
titleDOM.style.color = title.color
}
function renderContent (content) {
const contentDOM = document.getElementById(‘content’)
contentDOM.innerHTML = content.text
contentDOM.style.color = content.color
}
let appState = {
title: {
text: ‘React.js 小书 ’,
color: ‘red’,
},
content: {
text: ‘React.js 小书内容 ’,
color: ‘blue’
}
}
function stateChanger (state, action) {
switch (action.type) {
case ‘UPDATE_TITLE_TEXT’:
state.title.text = action.text
break
case ‘UPDATE_TITLE_COLOR’:
state.title.color = action.color
break
default:
break
}
}
const store = createStore(appState, stateChanger)
store.subscribe(() => renderApp(store.getState())) // 监听数据变化
renderApp(store.getState()) // 首次渲染页面
store.dispatch({type: ‘UPDATE_TITLE_TEXT’, text: ‘《React.js 小书》’}) // 修改标题文本
store.dispatch({type: ‘UPDATE_TITLE_COLOR’, color: ‘blue’}) // 修改标题颜色
至此,我们已经大概构建了一个 redux 的骨架,接下来我们将完善它
严重的性能问题
不知道读者有没有发现,当我们通过 dispatch 修改标题的文字时,整个 App 就会刷新一次,当我们修改文本的颜色时,整个 App 也会刷新一次,这样就频繁的全部刷新就造成了极大的性能问题那么,能否修改 title,仅刷新 title;修改 content,也仅刷新 content 呢?我们使 render 函数接收 2 个参数 (newState, oldState = {}) 并且在刷新前进行比较
function renderApp (newAppState, oldAppState = {}) {// 防止 oldAppState 没有传入,所以加了默认参数 oldAppState = {}
if (newAppState === oldAppState) return // 数据没有变化就不渲染了
console.log(‘render app…’)
renderTitle(newAppState.title, oldAppState.title)
renderContent(newAppState.content, oldAppState.content)
}
function renderTitle (newTitle, oldTitle = {}) {
if (newTitle === oldTitle) return // 数据没有变化就不渲染了
console.log(‘render title…’)
const titleDOM = document.getElementById(‘title’)
titleDOM.innerHTML = newTitle.text
titleDOM.style.color = newTitle.color
}
function renderContent (newContent, oldContent = {}) {
if (newContent === oldContent) return // 数据没有变化就不渲染了
console.log(‘render content…’)
const contentDOM = document.getElementById(‘content’)
contentDOM.innerHTML = newContent.text
contentDOM.style.color = newContent.color
}
这样就可以提高性能了吧,每次只刷新需要刷新部分啦~~
才怪!我们确实修改了对象内的属性值,但是 newState 和 oldState 所指的不还是一个对象吗?所以为了进行判断,我们还要修改前面的 stateChanger 函数
function stateChanger (state, action) {
switch (action.type) {
case ‘UPDATE_TITLE_TEXT’:
return {// 构建新的对象并且返回
…state,
title: {
…state.title,
text: action.text
}
}
case ‘UPDATE_TITLE_COLOR’:
return {// 构建新的对象并且返回
…state,
title: {
…state.title,
color: action.color
}
}
default:
return state // 没有修改,返回原来的对象
}
}
现在我们才真正提高了性能
reduer
为了让程序的结构更加清晰,我们把原始 state 放入 stateChanger 中,并把 stateChanger 改名为 reducer
为什么叫 redcer?别问为什么,没有理由!
function reducer (state, action) {
if (!state) {
return {
title: {
text: ‘hello world’,
color: ‘red’,
},
content: {
text: ‘hello world content’,
color: ‘blue’
}
}
}
switch (action.type) {
case ‘UPDATE_TITLE_TEXT’:
return {
…state,
title: {
…state.title,
text: action.text
}
}
case ‘UPDATE_TITLE_COLOR’:
return {
…state,
title: {
…state.title,
color: action.color
}
}
default:
return state
}
}
总结
现在的代码和 react,wepy 关系都不大,在下一篇文章中,我会讲述如何具体地在 react 中使用 redux 感谢 @胡子大哈 老师的《react 小书》,本章有很多代码都是摘自该书
本文参考
react 小书