Redux 源码解析系列(一) -- Redux的实现思想

前言

最近有同学问我Redux的一些知识,所以我又把我以前写的源码分析的文章搬出来啦,redux的源码都很简单,很适合初学者学习。

虽然最近hooks已经出来了,但是在它真的能替代redux之前,我们还是要好好学习redux,顺便大家可以了解为啥需要redux这样的状态管理工具,对分析比较hooksredux也是有好处的。

下面我们就开始吧~(PS:这些都是我刚入职写的文章,若有问题,欢迎随时提出纠正~)

作者:daisy,原文:https://zhuanlan.zhihu.com/p/57209324

Redux简介

Redux 其实是用来帮我们管理状态的一个框架,它暴露给我们四个接口,分别是:

  • createStore

  • combineReducers

  • bindActionCreators

  • applyMiddleware

  • compose

源码系列里会分别对这五个接口进行解析。

在对Redux 的源码解析系列开篇之前,先来了解一下它的实现思想。

为什么要有dispatch

假设一种场景下,app里每个组件都需要拿到appState{title: 'redux', color: 'red' }的一部分进行渲染。

但是这里存在一个风险就是,谁都可以修改appState的值。换句话说,有一天当appState变了你都不知道是谁改的,所以我们需要有一个管理员来帮我们管理我们的状态,这时候引入了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
  }
}

解决问题:既可以解决组件共享问题,同时不会有数据可以被任意修改的问题。

为什么要有createStore

现在我们有了状态,又有了dispatch,这时候我们需要一个高层管理者store,帮我们管理好他们,

所以我们就有了createStore这个函数帮我们生成store, 这样再用的时候就可以直接store.getState, store.dispatch的方式获取和更改组件状态

function createStore(state, stateChanger) {
  const getState = () => state;
  const dispatch = (action) => stateChanger(state, action)
  return {getState, dispatch}
}

createStore 接受两个参数,一个是表示app的 state。另外一个是 stateChanger,它来描述应用程序状态会根据 action 发生什么变化,其实就是相当于本节开头的 dispatch 代码里面的内容,我们后来会将它命名为reducer。

到了这一步,每当我想状态发生改变的时候,我就dispatch一个action来改变组件当前的状态。

但是这里还有一个问题,就是store里的数据发生改变之后,react是感知不到的。

如图:

所以我们需要手动在重新render一次APP。

整个过程如下:

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

function createStore(state, stateChanger) {
  const getState = () => state;
  const dispatch = (action) => stateChanger(state, action)
  return {getState, dispatch}
}


const store = createStore(appState, stateChanger)

renderApp(store.getState()) // 首次渲染页面
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《Redux》' }) // 修改标题文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色

// 注意:这里需要我们手动重新render
renderApp(store.getState()) // 把新的数据渲染到页面上

这时候就需要观察者模式,react订阅store数据的改变,然后自动调用renderApp。我们的把这个功能加入在store里,所以createStore功能又强大啦~

function createStore(state, reducer) {
  const getState = () => state;
  const listeners = [];
  // 订阅函数
  const subscribe = (listener) => {
    listeners.push(listener)
  } 
  const dispatch = (action) => {
    reducer(state, action);
    // 数据已发生改变就把所有的listener跑一遍
    listeners.forEach((listener) => {
      listener()
    })
  }

  return {getState, dispatch, subscribe}
}

接着,我们就可以这样使用

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

// stateChanger 实际就是我们的reducer
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)
// 监听数据变化,每次数据改变都会自动执行renderApp
store.subscribe(() => renderApp(store.getState())) 

renderApp(store.getState()) // 首次渲染页面
store.dispatch({ type: 'UPDATE_TITLE_TEXT', text: '《Redux》' }) // 修改标题文本
store.dispatch({ type: 'UPDATE_TITLE_COLOR', color: 'blue' }) // 修改标题颜色

这样,我们的renderApp 就会监听状态变化,每当dispatch一次action,它都会被自动执行。

流程图如下:

到这一步,一个APP就已经可以无压力的跑起来啦,最后一步,当然是关注性能,我们这个app 还是有严重性能问题的,因为每一次的dispatch 一次action,不管数据有没有变化,组件都会被重新渲染,这当然是不必要的。

为什么reducer是纯函数

所以就需要对reducer产生的前后appState进行一个对比,这就要求reducer必须是一个纯函数,返回的是一个新的object,这样才能够对比可以通过对比前后的state是否相等,来决定是否re-render

// reducer用来管理状态变化
function reducer (state, action) {
  switch (action.type) {
    case 'CHANGE_TITLE':
      return {
        ...state,
        title: {
          ...state.title,
          text: action.text
        }
      }
    case 'CHANGE_CONTENT':
      return {
        ...state,
        content: {
          ...state.content,
          color: action.color
        }
      }
  }
}

function createStore(state, reducer) {
  let appState = state;
  const getState = () => appState;
  const listeners = [];
  const subscribe = (listener) => {
    listeners.push(listener)
  } 
  const dispatch = (action) => {
    // 覆盖原先的appState,可以看到:由于reducer返回的是一个新的object,那在外层,
    // 我们就可以通过对比nextProps跟t his.props 来决定是否重新渲染
    appState = reducer(state, action);
    listeners.forEach((listener) => {
      listener()
    })
  }

  return {getState, dispatch, subscribe}
}

OK,到这一步,我们的redux就基本完成啦~ 接着改装下我们的reducer,让它有一个初始值,这样我们的createStore就只需要传入一个reducer即可

function reducer (state, action) {
//设置初始值
  if(!state) {
    return appState;
  }
  switch (action.type) {
    case 'CHANGE_TITLE':
      return {
        ...state,
        title: {
          ...state.title,
          text: action.text
        }
      }
    case 'CHANGE_CONTENT':
      return {
        ...state,
        content: {
          ...state.content,
          color: action.color
        }
      }
  }
}
function createStore (reducer) {
  let state = null
  const listeners = []
  const subscribe = (listener) => listeners.push(listener)
  const getState = () => state
  const dispatch = (action) => {
    state = reducer(state, action)
    listeners.forEach((listener) => listener())
  }
  dispatch({}) // 初始化 state
  return { getState, dispatch, subscribe }
}

总结以下:store对象里的方法主要做三件事

  1. getState :获取组件状态

  2. dispatch :改变组件状态

  3. subscribe : 订阅组件变化

如何使用redux

需要五个步骤


// 1、定一个 reducer, 负责管理数据变化还有初始化appState的数据
function reducer (state, action) {
  /* 初始化 state 和 switch case */
}

// 2、生成 store
const store = createStore(reducer)

// 3、监听数据变化重新渲染页面
store.subscribe(() => renderApp(store.getState()))

// 4、首次渲染页面
renderApp(store.getState()) 

// 5、后面可以随意 dispatch 了,页面自动更新
store.dispatch(...)

我们整个过程就是不断地发现问题,解决问题

1、共享状态 -> dispatch

2、store统一管理 -> dispatch, getState

3、性能优化 --> reducer是一个纯函数

4、最终初始化整个reducer

以上就是redux的大致思想。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值