阅读 1569

用少量代码实现redux核心部分及其演化历史

作者:殷荣桧@腾讯

本文源代码地址

本文Github地址,欢迎star

  因为redux是基于flux模式的,所以如果你想对flux有所了解,可以查看我的这篇文章Vuex和Redux都使用的Flux设计模式精简版实现

  先来看一下,完成文章标题所说的,需要完成哪些任务:(看到下面这个list,先不要被吓跑,因为每个任务平均实现代码都没有25行,只是为了分的细一点)

TODO LIST(计划列表)

1.redux中reducer的实现

2.redux中action的实现

3.redux中store的实现

3.5 先不使用redux,直接用react实现一个主题定制页面的开发

  3.5.1 使用props传参完成父子组件共享同一个全局变量(演示全局变量的原始层层传递共享数据)
    
  3.5.2 使用react提供的Context上下文完成父子组件全局变量的共享(演示全局变量使用react context 跨越组件共享数据) 

4.结合自己实现的redux实现一个主题定制页面的开发

5.redux中的connect的实现。(结合react中的context实现)

6.结合connect react把主题定制页面再实现一遍

7.redux中添加mapStateToProps

8.redux中添加mapDispatchToProps

9.结合mapStateToProps,mapDispatchToProps 主题定制页面

10.redux中的provider的实现

11.使用provider重构代码

12.使用真正的react-redux替代进来,看项目是否正常运行。代码整理,provider中页面刷新相关的移动到connect,语法检查,错误检查,添加注释,合并commit
复制代码

  接下来我们试着一个一个去实现。当然这其中包括了为什么需要redux的历史演化的过程,总的用了十几个commit来完成了这篇文章,基本上每个commit实现TODO list中的一个计划(在这墙裂推荐你使用source tree这个工具来查看各个commit都干了啥,修改了啥,对阅读源代码很有帮助)。在完成一个完整的功能后都会使用完整的demo进行演示,基本上可以覆盖redux的进化历程了。最后真正实现redux的部分连100行代码都不到,所以你千万不要被redux的名头吓跑。最后再不要脸一下,如果你觉得文章写得还行,欢迎您在我的Github中送上star.

接下来,就照着项目提交的commit一个一个的讲:

(1)commit1:完成TODO list规划 这个commit主要是完成了实现这篇文章所要完成的任务。也就是上面陈列的TODO list.

(2)commit2:完成reducer模块的功能 没有使用任何的构建工具,直接打开index.html就可以运行。需要在Google 调试工具的控制台中查看结果,界面上并无内容。这个commit主要就是实现了一个reducer,并在index.html中调用了reducer.js,主要是调试了一下reducer是否可以正常的运行。reducer实现的代码部分如下。

var reducer = (state = initialState, action) => {
    switch(action.type) {
        case CREATE_NOTE: {
            let currentId = state.nextNoteId;
            return {
                nextNoteId: currentId + 1,
                notes: {
                    ...state.notes,
                    [currentId]: action.content
                },
            };
        }
        case UPDATE_NOTE: {
            let {id, content} = action;
            return {
                ...state,
                notes: {
                    ...state.notes,
                    [id]: content
                }
            };
        }
        default:
            return state;
    }
}
复制代码

调试成功的结果如下:

(3)commit3-commit4:redux中action的实现

这两个commit主要实现了redux中action的定义。同样在index.html中引入了actions.js来对所写的action进行测试。

const CREATE_NOTE = "CREATE_NOTE";
const UPDATE_NOTE = "UPDATE_NOTE";

// 添加一条备忘录
let create_action = {
    type: CREATE_NOTE,
    content: '明天下午要开会'
};
// 更新一条备忘录
let update_action = {
    type: UPDATE_NOTE,
    id: 1,
    content: '好像记错了,是明天上午要开会'
};
复制代码

下图为测试成功控制台的结果。

(4)commit5-commit6:完成store的实现,基本实现redux功能

function createStore(reducer) {
    let state = undefined;
    const subscribers = [];
    let store = {
        getState: () => state,
        dispatch: (action) => {
            state = reducer(state, action);
            subscribers.forEach(handler => handler());
        },
        subscribe: (handler) => {
            // handler就要比如备忘录有更新就发送通知到对应的人之类的
            subscribers.push(handler);
            return () => {
                let index = subscribers.indexOf(handler);
                if (index >= 0) {
                    // 防止搜索不到index为-1时,把subscribers最后一个删除了
                    subscribers.splice(0, index);
                }
            }
        }
    }
    store.dispatch(init_action); // 初始化一下备忘录
    return store;
}
复制代码

基本实现redux后,来测试一下自己写的redux是否正常运行。以下为正常运行的结果:

(5)commit7:使用props传参完成父子组件通信,完成主题切换 使用react的项目初始化工具,详情参考react项目初始化工具,完成项目的初始化工作,并使用最原始的方式进行父子组件的传参,使用了props进行传参。使用这种方式的弊端就是,如果要从父向儿子,孙子,重孙....一直传到祖宗十九代的时候,每个子组件都要写向下传递的代码,非常的冗余和难以维护。在项目中才使用了几层代码都能感觉到代码的冗余和难以维护。(注意:项目中的Index.js需要修改为index.js才可以在my-app目录下使用npm run start运行,我的锅,不要意思,之后的commit都要改成index.js才可以正常运行)

 class Content extends Component {
  render() {
    return (
      <div>
          <span 
            color={this.props.color} 
            style={{color: this.props.color}}>
              主内容区域
          </span>
          <br/>
          <ChildContent 
            switchColor={this.props.switchColor}
            color={this.props.color} 
            ></ChildContent>
      </div>
    );
  }
}
复制代码

以下是完成的主题切换的效果:

(6)commit8:完成react提供的Context上下文完成父子组件全局变量的共享 为了避免代码的冗余。可以考虑使用react中提供的全局context来进行完成。这样就可以避免层层使用props来进行传参。在任何一行使用contextTypes就可以完成引用全局变量。但是这样写代码的弊端依然很明显。代码中依然存在冗余。但维护性相对于使用props较好。以下是使用contextTypes在组件中引用属性的方法。

class Title extends Component {
    static contextTypes = {
        color: PropTypes.string
    }
    render() {
        return (
            <div style={{color: this.context.color}}>我是文章的标题</div>
        );
    }
}
复制代码

(7)commit9-commit10:结合自己实现的redux实现一个主题定制页面的开发

到这就可以结合自己开发的redux实现一个简单的主题定制页面的开发。主要在红色主题与蓝色主题之间切换。来测试自己所写的redux是否可以正常工作。主要实现的结果和上面相同。通过在my-app/的目录下运行npm run start就可成功启动页面。

(8)commit11-commit13:redux中connect的实现

connect实现部分的代码如下:

let connect = (WrappedComponent) => {
    class connect extends Component {
        static contextTypes = {
            store: PropTypes.object,
        }
        componentWillMount() {
            this.store = this.context.store;
        }
        render() {
            return <WrappedComponent store = {this.store}></WrappedComponent>   
        }
    }
    return connect;
}
复制代码

(9)commit14:redux中mapStateToProps的实现

mapStateToProps实现的代码如下:

let mapStateToProps = (state) => {
    return {
        themeColor: state.color,
    }
}
复制代码

(10)commit15:redux中mapDispatchToProps的实现并结合自己实现的mapStateToProps和mapDispatchToProps更新主题修改小应用

let mapDispatchToProps = (dispatch) => {
    return {
        changeThemeColor: function(color) {
            dispatch({type: UPDATE_THEME, color})
        } 
    }
}
复制代码

(11)commit16:redux中provider的实现,并使用其更新主题修改小程序

class Provider extends Component {
    static propTypes = {
        store: PropTypes.object,
    }
    static childContextTypes = { // 定义父子组件共享的变量
        store: PropTypes.object,
    }
    getChildContext () {
        return {
          store: this.props.store
        }
    }
    componentWillMount() {
        this.props.store.subscribe(() => this.updateComponent());
    }
    updateComponent() {
        // 每次store数据更新后重新渲染一下页面
        this.setState({color: this.props.store.getState().color});
    }
    render() {
        return this.props.children;
    }
}
复制代码

(12)commit17-commit18:完成redux的简单仿写,并将项目中引用自己所写redux部分全部改成react中的redux,好看代码是否可行。

-import createStore from './redux/store';
+import {createStore} from 'redux';
-import Provider from './redux/provider';
+import {Provider} from 'react-redux';
复制代码

通过上述的替换之后,切换主题的小应用工作正常(需要使用命令npm run dev,手贱,把start改成了dev),如下图所示。可见所仿写的redux基本成功,当然其中精细部分与原版有所差别,但是整体的原理上基本相似。所以到这基本上就了解了redux的工作原理,以后再也不要对redux的语法死记硬背了,因为你都已经写过一遍。

本文源代码地址

本文Github地址,欢迎star

  部门正在招新,为腾讯企业产品部,隶属CSGI事业群。福利不少,薪水很高,就等你来。有兴趣请猛戳下方两个链接。    www.lagou.com/jobs/521039… www.zhipin.com/job_detail/…

(如果发现文章中代码存在问题,请批评指正并留言,我会更新到文章以及代码中去,谢谢)

参考资料

code-cartoons.com/a-cartoon-i… cartoon intro to redux) zapier.com/engineering…(build yourself a redux) www.sohamkamani.com/blog/2017/0… (React-redux "connect" explained) codesandbox.io/s/github/re…(provider and connect example) huziketang.mangojuice.top/books/react… (React.js 的 context) huziketang.mangojuice.top/books/react… (动手实现 Redux(四):共享结构的对象提高性能,为什么需要使用...(spread operator)来提供性能) juejin.im/post/5a90e0…(聊一聊我对 React Context 的理解以及应用)

关注下面的标签,发现更多相似文章
评论