啊好久没有更新博客了,罪过罪过,前段时间开了个头的文章周末把它补完了。
简介
React.js 是一个帮助我们构建页面 UI 的库,可以使我们将页面拆分成多个独立的组件,我们可以将这些组件进行各种复用,嵌套,组合,来构成最终的页面,大大的提高开发和维护的效率。但它本身只是提供了 View 层的解决方案,在实际的项目中,我们往往需要结合 Redux,React-router 等来作为最后的方案。
注:本文主要对 React 开发中的一些重要概念进行简单的总结,并不结合代码来详细讲解。
JSX
传统的 jQuery 开发往往基于对页面中真实 DOM 的操作,但交互逻辑和数据处理越来越复杂的 web 应用中,这种方式使得开发和维护的成本越来越高, 大量的人工对 DOM 的操作也很容易因为程序员水平的参差不齐而引入低效和低性能的代码。而 React 的核心,则是基于使用 Javascript 对象的数据结构来表达真实的 DOM,让所有的操作先通过快速的 JS 引擎中运算,再将最后的结果进行 diff 比较,最后才实现到真实的 DOM 树之上。直接通过 JS 中的 Object 实际上就可以表达一个 DOM 元素的结构信息,但是开发中如果所有的 html 结构都用 object 来书未免太长了,因此这里引入了一种叫 JSX 的扩展语言,使用类似 html 的标签结构来书写,在编译时再将其转换为 JS的对象。
Component
组件可以理解为我们将页面中的元素拆分成一个又一个独立的小块,在 React 中,其实类似 JS 中的函数,接受任意的 props,最后通过 render 方法来返回一个 JSX 对象,通过将各种组件的组合,来组成最终的页面。
得益于 JSX 的引入,我们可以像编写 html 代码一样来构造页面的结构,同时也可以在其中方便的插入 js 代码来实现条件,循环,函数执行,表达式计算来进行灵活的流程控制。
在 JSX 中,不光可以使用原有的 html 元素,也可以引入自己编写的组件来当作 html 元素,需要注意的是,原生的 html 元素都是小些,而自定义的组件元素首字母必须大写。在组件内部可以任意使用其他的组件,最后不断的组合嵌套,就构成了一棵庞大的组件树。
组件内部也可以有自己需要保存的 state 的信息,可以通过 setState 方法来修改其内部的状态。但外部传入的 props 则是无法自行修改的。之前提到的组件即是函数,React 中需要将所有的组件都作为纯函数来对待 props,永远不要直接去修改 props。
state 和 props 都可以改变组件的显示形态和行为,组件可以将自己的 state 作为 props 来传给子组件,也可以将外部传入的 props 作为初始的 state,但他们之间的职责其实是很清晰的,state 是让组件自己控制自己的状态,而 props 则是让从外部来配置组件。过多的内部状态会带来管理的复杂度,因此我们在编写组件的时候应该尽量多的通过 props 来控制的无状态的组件,来增强组件的复用性和降低状态管理的复杂度。
组件也有这从初始化,到构造 JSX 对象,到插入到真实 DOM 树,最后被移除这样一个生命周期。React 也为我们提供了各种各样的生命周期函数来进行更好的控制。
Higher-order component
HOC 实际上并非 React 的 API,而是一种复用组件逻辑的技巧。一句话总结,所谓的 HOC 就是一个函数,它接受一个组件作为参数,你可以在函数的内部对这个组件进行各种逻辑的封装,最后返回一个新的组件。在很多 React 相关的第三方库中,都大量的运用了 HOC,项目中的开发时,也可以使用 HOC 来封装重复的逻辑,减少代码量。也可以通过多层的高阶组件来将分离的单独的逻辑组合到一起,比如 recompose 就是专门来进行这种处理的一库。这种方式其实就是设计模式中的装饰者模式,得益于 React 中的函数式组件,我们可以大量的运用函数式编程的范式来进行开发,通过各种组合来达到非常高的灵活程度。当然灵活的掌握 HOC 的用法还是需要大量经验的累积和长时间的思考和练习。
Context
关于 React 中的状态管理其实都可以单独写一本书了。React 组件是只有 state 和 props 来控制状态的,在一个树形结构的组件树中,如果我们需要多个组件共享一些状态,只能将这些状态数据放到他们最近的公共的祖先元素之中,作为 props 一层一层往下传递,这就叫做状态提升。但在一个复杂的应用中,这种做法无疑是及其麻烦并且几乎无法维护的,因此 React 也提供了一个 context 的 API,只要在父级的组件中声明了 getChildContext,在后代的组件就中就都能够通过 context 来访问其中的数据。但是在我们的实际项目中,最好永远都不要直接手动使用 context这一 API,就好像全局变量一样,这一特性其实是非常危险的。但这种机制的存在使得我们可以使用诸如 Redux 一类的状态管理库来进行管理。
Redux
我们知道了 context 可以在 react 中存储状态,但直接使用 context 就好比使用全局变量一样,使得任意的子组件都可以接触或修改其中的数据,这种做法就很容易带来很多无法控制的情况了,出错的时的 debug 也非常困难。这个时候我们就引入了 redux 这种思想。
我们将所有的数据,即,之前放在context 中的状态,放入到一个单一的树形的 store 对象中,而我们要作出的修改,都不能直接通过赋值一样的方法来进行,而是需要把修改用一个对象表示出来,这个对象,就叫做 action,然后把这个 action 传递给一个函数,这个函数由我们自己来编写,决定每一个对应的修改需要做出什么要的操作。这个函数就叫做 reducer,reducer 是一个纯函数,他接受先前的 state 和 action,并返回一个新的 state。因为对于一连串的修改,我们可以像数组的 reduce 方法一样将修改都放到这个 reducer 中来处理。把 action 传递给 reducer 的方法,就是 dispatch 了。
React-redux
在 React 中我们要把 redux 概念中存放状态的 store 和 context 这一 API 结合起来,就需要 react-redux 这一具体实现了。
由于直接让组件使用 context 会造成对 context 依赖过高的问题,影响在其他地方复用,因此我们考虑在组件和 context 之间,多加一层,用他来负责帮我们从 context 获取到数据, 通过 props 来传递给组件,这样我们的组件也可以做到只依赖于 props,像纯函数一样没有什么副作用,这种就叫 dump component,他的复用性也最强,只需要看需要什么 props,我就给它这些 props。
这样多加的中间一层,实际上就是一个 HOC,名字就叫 connect,因为它负责的就是把 dump component 和 context 连接起来。还有一个问题是,一个组件肯定是不需要 context 中所有的 state,因此我们需要告诉 connect 这个函数,需要把哪些数据从 context 中取出来,为了解决这个问题,我们可以多给 connect 传入一个函数,这个函数呢,接受 state 作为参数,然后返回一个根据 state 生成的对象,这就相当于告诉了 connect 如何从 store 里取数据,这个函数的名字,我们通常就叫 mapStateToProps。
当然,我们除了获取状态数据之外,同样有时候也需要去改变它,类似的,我们也可以可以通过传递给 connect 另一个函数告诉它组件需要如何去触发 dispatch,这个函数就不接受 state 作为参数了,而是接受 dispatch 作为参数,然后返回一个对象,告诉 connect 来 dispatch 一个什么样的 action,这个函数的名字就叫 mapDispatchToProps。
因此我们最后的用法就是 connect(mapStateToProps, mapDispatchToProps)(component)
,我们不需要去关心 connect 是如何与 context 沟通的,我们只要把需要的 state 和 dispatch的action 定义好,然后直接传进去就可以用了。
最后还有一个地方,就是在我们的组件树的顶级,我们仍然需要来定义 context 为 store,这里也可以额外定义一个 HOC 来做这种活,因为它提供了 store 来作为整个组件树的 context,因此名字就叫 Provider。
因此在 React-redux 的帮助下,我们就完全不需要自己去碰触 context 相关的东西。