「React全家桶系列」之Redux源码解析

712 阅读3分钟

redux使用数据流程如下:

由上可知,我们需要一个函数来保存状态值,并暴露出修改,获取,同步的方法,下面开始进行推理

准备工作

没有react-create-app,安装后再创建reduxLearn

npm install -g create-react-app


react-create-app reduxLearn

开始操刀制作新鲜的redux

关于createStore

删除publicsrc下所有的文件,创建reduxLearn/public/index.html

新建reduxLearn/src/index.js

设置一个变量来保存我们想要的效果

let state = {
    title: {
        color: 'red',
        text: '标题'
    },
    content: {
        color: 'pink',
        text: '内容'
    }
}

创建渲染方法

function renderApp(appState) {
    renderTitle(appState.title)
    renderContent(appState.content)
}
function renderTitle(title) {
    let titleEle = document.getElementById('title')
    titleEle.innerHTML = title.text
    titleEle.style.color = title.color
}
function renderContent(content) {
    let contentEle = document.getElementById('content')
    contentEle.innerHTML = content.text
    contentEle.style.color = content.color
}
function render() {
    renderApp(state)
}

如果在执行 renderApp(initState)之前的某个地方执行了initState=null

页面就会报错 TypeError: Cannot read property 'title' of null

为了是initState不被胡乱修改,我们需要搞个函数把它保护起来createStore

function createStore() {
    let state = {
        title: {
            color: 'red',
            text: '标题'
        },
        content: {
            color: 'pink',
            text: '内容'
        }
    }
}

变量被保护起来了,需要用的时候怎么获取呢?

需要提供getState暴露出变量的结果

function createStore() {
    let state = {
        title: {
            color: 'red',
            text: '标题'
        },
        content: {
            color: 'pink',
            text: '内容'
        }
    }
     //获取状态
     function getState() {
        return state
    }
    return {
      getState
    }
}

//使用状态
let store = createStore(reducer)
...
function render() {
    renderApp(store.getState())
}

如果我们想改变这个状态的值怎么办呢?

提供dispatch,但是你要需要告诉我改变什么,怎么改变即action

  • 定义类型

    const TITLE_COLOR = 'TITLE_COLOR' const TITLE_TEXT = 'TITLE_TEXT' const CONTENT_COLOR = 'CONTENT_COLOR' const CONTENT_TEXT = 'CONTENT_TEXT'

  • 创建dispatch方法,暴露出来

    //派发 function dispatch(action) { switch (action.type) { case TITLE_COLOR: state.title.color = action.color break; case TITLE_TEXT: state.title.text = action.text break; case CONTENT_COLOR: state.content.color = action.color break; case CONTENT_TEXT: state.content.text = action.text break; default: return state } }

  • 调用dispatch

    store.dispatch({ type: TITLE_COLOR, color: 'blue' }) store.dispatch({ type: TITLE_TEXT, text: '修改标题了' }) render()

  • 看效果

在实际开发中初始状态state和操作步骤dispatch不是固定的我们需要提取出来

function createStore(reducer) {
    let state;
    //派发
    function dispatch(action) {
        state = reducer(state, action)//通过reducer处理返回结果值
    }
    //获取状态
    function getState() {
        return state
    }
    //执行一次,目的是设置默认值
    dispatch({ type: "@@TYPE_INIT_STATE" })
    return {
        dispatch,
        getState,
    }
}
  • 现在开始写reducer 函数,需要状态值和操作类型两个参数

    //声明初始值 const initState = { title: { color: 'red', text: '标题' }, content: { color: 'pink', text: '内容' } } //reducer 这里返回的数据应是新对象->state = reducer(state, action)//通过reducer处理返回结果值 function reducer(state = initState, action) { switch (action.type) { case TITLE_COLOR: return { ...state, title: { ...state.title, color: action.color } } case TITLE_TEXT: state.title.text = action.text return { ...state, title: { ...state.title, text: action.text } } case CONTENT_COLOR: state.content.color = action.color return { ...state, content: { ...state.content, color: action.color } } case CONTENT_TEXT: return { ...state, content: { ...state.content, text: action.text } } default: return state } }

  • 修改引用

    let store = createStore(reducer)

发现每次修改都要重新调用render()才生效,很麻烦,如果我们在每次修改时添加监听事件,触发订阅模式,则可以一劳永逸

添加订阅函数

  • 订阅函数

    function createStore(reducer) { let state; //订阅添加监听函数数组 let listeners = [] //派发 function dispatch(action) { state = reducer(state, action) //订阅 listeners.forEach(l => l()) } //订阅,返回一个取消订阅的方法 function subscribe(listener) { listeners.push(listener) return function () { listeners = listeners.filter(item => item != listener) }

    }
    //获取状态
    function getState() {
        return state
    }
    dispatch({ type: "@@TYPE_INIT_STATE" })
    return {
        dispatch,
        getState,
        subscribe
    }
    

    }

  • 调用时订阅函数

    store.subscribe(render)

  • 测试订阅函数和取消订阅函数

    store.subscribe(render)

    setTimeout(() => { store.dispatch({ type: TITLE_COLOR, color: 'blue' }) store.subscribe(render)()//取消订阅 store.dispatch({ type: TITLE_TEXT, text: '修改标题了' }) }, 1000);

  • 效果如下

提取createStore函数

  • 新建reduxLearn/src/redux/createStore.js

    export default function createStore(reducer) { let state; let listeners = [] //派发 function dispatch(action) { state = reducer(state, action) //订阅 listeners.forEach(l => l()) } //订阅,返回一个取消订阅的方法 function subscribe(listener) { listeners.push(listener) return function () { listeners = listeners.filter(item => item != listener) }

    }
    //获取状态
    function getState() {
        return state
    }
    dispatch({ type: "@@TYPE_INIT_STATE" })
    return {
        dispatch,
        getState,
        subscribe
    }
    

    }

  • 新建reduxLearn/src/redux/index.js

    import createStore from './createStore' export { createStore, }

  • 在使用时直接调用就可以使用了

    import { createStore } from './redux'

createStore总结

要想保护数据要找个函数包裹起来createStore,可变的因素reducer可以通过参数传递进来,对数据的操作可以在函数内部暴露给外面getstate,dispatch,subscribe

在使用时直接把使用函数赋值给变量store,使用变量.函数store.getState()即可

项目地址:

github.com/XinYueXiao/…