返回 登录
10

React+Redux技术栈核心要点解析(上篇)

阅读4459

感谢作者郭永峰的授权发布。
作者:郭永峰,前端架构师,现用友网络 FED团队负责人,目前主要负责企业级应用前端技术平台建设工作,在前端工程化实现、Node 应用开发、React技术、移动开发等方向有丰富实践经验。Github地址:https://github.com/GuoYongfeng
责编:陈秋歌,关注前端开发等领域,寻求报道或者投稿请发邮件至chenqg#csdn.net。

使用 React + Redux 这个技术栈开发应用已经有很长一段时间了,我的一些使用经验也许会有些主观,但我觉得写出来也许对你开始学习或是进阶使用 React + Redux 会有些帮助。Redux 并不是只和 React 结合使用的,它也可以和其他的很多类库结合起来一起使用,即使你还未开始深入使用,你也可以阅读文中的部分内容。同时,如果你有一些建议或是疑惑,可以在 Github 给我提交 Issue,很乐意与你一起交流。

对学习 React 的一些建议

在深入 Redux 、Testing 或是其他更高级的使用之前,我们还是先开始 React 吧。

不要太过在意脚手架

你已经准备好开始学习 React 了吗,我建议你不要一开始就进入到选择和学习项目脚手架的困扰中,因为你一上来就得接触Webpack、Babel、Testing Tool等,实在是眼花缭乱。

我建议你可以首先采用 create-react-app 这个工具,他是Facebook For React 官方提供的零配置CLI 命令行工具。从技术上来说虽然它依然是一个脚手架,但是它屏蔽了所有的工具配置,你可以用它来快速生成一个React项目所需的基本工程。

sudo npm install create-react-app -g
create-react-app react-demo
cd react-demo && npm install
npm start

仅仅以上几步,你就可以在启动开发了,非常的快吧。

但是等你想深入项目的工程化配置而不再想使用 create-react-app 的时候,那么,是时候该去学习一个适合你的构建工具了。在使用 create-react-app 这个工具的时候,它让你快速开发的同时也让你错过了如何真正的使用工具并去配置工具。

当你从零开始搭建一个你自己的项目工程的时候,那么你将需要从底层来了解这些技术是如何工作的,这样的话,慢慢就能产出一个你自己的项目脚手架了,在团队里面去推广使用。

小结:

  • 避免开始学习的时候就去使用脚手架;
  • 使用 create-react-app 去学习 React;
  • 当你熟悉 React之后
    • 去探索工具本身如何使用
    • 创建属于你自己的项目脚手架

在你学习 Y 的时候要先去学习 X

虽然学习 React 不难,因为它仅仅是个 view library,但是 React 的整个生态链非常丰富,并且会有很多关于如何学习的方法和资料。

而我的建议是,在你学习东西的时候需要学习这个技术的前置技术,否则学习起来会很吃力。所以你在开始学习这个技术栈的其他东西之前,下面所列举的内容应该会对你很有帮助:

  • JSX 语法
  • ReactDOM.render
  • 使用 setState 来改变组件的状态 state
  • 组件生命周期相关的方法
  • 事件和表单
  • 几种创建组件的方式
  • 复合组件 composeable
  • 高阶组件的使用和定义 HOC

一般建议你在开始下一部分内容之前,你可以先把这些 React 相关的内容都学习完成。

什么时候引入 Redux

在你遇到应用的状态管理问题之前,你可以通过扩展应用的方式来解决,或是你在你的应用中压根就没遇到过这样的问题,因为你可以使用 setState 来很好的管理你的应用。但其实 setState 也不是那么高效,毕竟它也不是万能的。也许你会发现你已经在忙于处理太多的组件内部状态,那么,该是时候引入类似 Redux 等状态管理库了,不过有几个事情需要注意:

  • 在学习 Redux 前得先学好 React
  • 可以阅读以下You might not need Redux
  • 不是所有的状态都需要放在 Redux 的 Store 中,也不能完全取代 setState

那关于 ES6 呢

大量的关于React的示例都采用ES6语法,也许你在其他的项目中已经学习并使用了 ES6 相关的语法和API,或者也许你还没接触呢。不过,ES6对于React也不是必须的,我的一些建议是:

  • 如果你有使用类似 angular 等前端框架或是类库的经验
    • 你可以在你熟悉的环境里面去学习 ES6
  • 如果你是前端入门的新人
    • 你可以在React中继续使用ES5的语法和API
    • 或者在你学习React之前,先学习一下ES6
  • 如果你是个前端老司机
    • 逐步在React应用中使用ES6吧

我比较建议的学习方式是,学一个东西之前一定要先掌握它相关的前置知识,另外,也一定要记住:不要一次性学习所有东西,不然反而啥都掌握不了。

声明式的React组件

一般地,会有三种不同的方式来声明一个组件:

  • React.createClass 使用这个API来定义组件
  • React ES6 class components 使用 ES6 的class 来定义组件
  • Functional stateless components 通过函数直接定义无状态组件
// React.createClass
var TodoItem = React.createClass({ ... })

// React ES6 class
class TodoItem extends React.Component { ... }

// functional stateless component
function TodoItem() { ... }

那什么时候该用什么方式来定义呢

无状态组件:适合用来声明没有组件生命周期方法并且没有内部状态的组件。这种写法很简单,就是一个纯函数,一个状态输入,输出就是elements。

# 示意的伪代码
(state) => View

这是一种最轻便最高效的组件声明方式,这种组件没有任何的内部状态,使用的时候也无法访问到组件的属性,所以一般建议能用这种方式声明组件的时候就尽量采用这种方式。

如果你需要使用组件生命周期方法,并且需要去处理组件内部状态(this.state) ,或者需要获取这个组件(this.ref)。那么,这个时候建议你使用ES6的 class 来声明组件。

另外,建议还是别用React.createClass这种方式声明组件了,Facebook 官方在React V0.13版本的时候就说过,以后的目标是使用ES6 classes的方式来定义,会完全废弃React.createClass

另外,这有两篇博文也推荐给你看:

轻量级的函数式无状态组件

项目中一定需要去声明很多组件,我们可以一起来完成一个 TodoList 组件:

function TodoList({ list }) {
  return (
    <div>
      {map(list, (item) => <div>{item.name}</div>)}
    </div>
  );
}

我们还可以按函数的方式将其拆分

function TodoList({ list }) {
  return (
    <div>
      {map(list, (item) => <TodoItem item={item} />)}
    </div>
  );
}

function TodoItem({ item }) {
  return <div>{item.name}</div>;
}

这个例子有些简单,无法很直接的看到这种定义方式的好处,但是当我们将组件拆分后将更具有可读性、可复用性以及可维护性。这种方式很灵活,而且很轻松就可以声明多个组件,一口气不费劲,推荐使用。

简洁的函数式无状态组件

我们可以使用 ES6 的 arrow functions 让组件的定义更加的清新。假如我们有一个这样的组件:

function Button({ onClick, children }) {
  return (
    <button onClick={onClick} type="button">
      {children}
    </button>
  );
}

我们可以将其用 ES6 的语法改写升级一下:

const Button = ({ onClick, children }) => {
  return (
    <button onClick={onClick} type="button">
      {children}
    </button>
  );
}

还可以再简单吗,我们来试一试:

const Button = ({ onClick, children }) =>
  <button onClick={onClick} type="button">
    {children}
  </button>

但这种方式就只允许我们的组件只有 props 作为输入,elements 作为输出。但如果我们希望在这中间做一些业务逻辑呢,我们可以将其再修改一下:

const Button = ({ onClick, children }) => {

  // do something

  return (
    <button onClick={onClick} type="button">
      {children}
    </button>
  );
}

回过头来看看,箭头函数真是在我们定义无状态组件的时候帮了大忙,整个世界的清爽了。

木偶组件Presenter Component和容器组件Container Component

好吧,这两个词听起来实在是别扭,听我慢慢道来。

React 中的组件其实也只是应用状态的一种表现方式,这让我们可以非常清晰的通过 (State) => View 这样的方式来理解。并且,组件内由处理程序来改变 state,从而改变不同 view 的展示。

那么,木偶组件和容器组件有什么区别或是怎么理解呢。

  • 木偶组件只接受传递进来的 props 和 callback,然后返回对应需要展示的 view,这种组件比较单纯,大部分都是无状态组件,给我啥我就展示啥,给我什么callback我就执行callback,相同的输入总能得到相同的输出,像个机器人或是像个木偶,很纯粹很简单很可控;
  • 而容器组件内更多的是关注逻辑,你需要在容器组件中准备好数据和一些callbac函数,在这里处理一些事件或管理内部的状态,给木偶组件传递所需的数据,大致的意思就是,容器组件对木偶组件说:调度或是逻辑处理的事就交给我吧,你需要啥我给你啥,你安心干你的活就行,保证给我的产出即可。

当项目中使用了Redux的时候,Container Component 有个更好理解的名字,就是Connected Component。这类组件和Redux的store进行了连接,并且获取到store的数据之后进行一些操作后传递给子组件。

Container components容器组件关注事情是怎么做的,Presenter components木偶组件关注怎么展现,各司其职,其乐融融。

另外,也再推荐给一篇文章smart-and-dumb-components,可以再更深入的理解。

什么时候该用 Container Components 容器组件呢

前面聊完,我想你应该知道两者的区别和使用场景了,但也许你还不太确定什么时候该用什么类型的组件。你可以定义一个Container Components,然后把一些Presenter Components都作为他的子组件,这样父组件关注如何工作,子组件关注如何展现。

但慢慢的,你发现父组件需要给子组件传递的properties和callbacks越来越多了,咋办呢,现在是时候来介绍两者如何结合使用了。

一般地,有个很好的原则:坚持presenter components不变,只新增一系列的container components去适应业务逻辑的改变。

把container component放到什么地方呢

  1. 父组件container component只关注state如何处理,现在你可以评估一下你的preenter component下面的子组件了。也许你会注意到preenter component下面的子组件没有被其他组件使用,那么找到这个组件的父组件,给它添加一个用于管理状态的container component,这样,父级的container component就能够变得更加清晰轻量,因为它没有必要去处理这些所有的状态。
  2. 很多presenters component也许只是包含一些只是为他们自己使用的props和callbacks,那么,也给他们加上一个container component去处理这些逻辑,把这些逻辑放到container component,这样将使父级的container component 再次变得轻量。

写出你的第一个高阶组件HOC吧

想象一下你需要展示一列内容,但是你不得不首先通过异步的方式获取这些内容项。现在你需要一个加载指示器来显示目前正在发送请求中,等请求完成后,你再展示获取到的内容项。

不过你可以更进一步来学习HOC了,一个高阶组件HOC将会返回一个增强的函数。

// 定义一个高阶组件

function withLoadingSpinner(Component) {
  return function EnhancedComponent({ isLoading, ...props }) {
    if (!isLoading) {
      return <Component { ...props } />;
    }

    return <LoadingSpinner />;
  };
}

// 使用

const ListItemsWithLoadingIndicator = withLoadingSpinner(ListItems);

<ListItemsWithLoadingIndicator
  isLoading={props.isLoading}
  list={props.list}
/>

高阶组件很强大,但是我们也得有目的的去使用。另外,recompose提供了一系列非常有用的高阶组件,在开始写你自己的高阶组件的时候,不妨先去这里看看,学习一下,也许就能解决你的疑惑了。

带有业务逻辑的样式名管理

也许你在组件中遇到了这样带有条件逻辑的样式名定义:

var buttonClasses = ['button'];

if (isRemoveButton) {
  buttonClasses.push('warning');
}

<button className={buttonClasses.join(' ')} />

这种情况我们可以使用 classnames 来处理,这会让我们可以非常方便的在elements上定义有条件的style:

import classNames from 'classnames'

var buttonClasses = classNames(
  'button',
  {
    'warning': isRemoveButton
  },
);

<button className={buttonClasses} />

React 中的动画 Animations

通过这个小例子来感受一下React animationreact-motion 给我们提供了一个在 react 中使用动画效果的工具包。但是,我发现这个学习曲线非常陡峭,在你开始使用 React Motion 之后就会让人觉得非常沮丧。不过一旦你写出一个流畅的可拖放动画之后你会感觉很有成就感。

其实,对于大多数前端小伙伴们来说,我们也只是花很少一部分时间在动画效果的实现上面,所以当你本身不会经常使用这个动画库的时候,等到下次需要用到的时候又得重新熟悉一遍,这也造成了学习曲线反复的问题。

另外,velocity-react 是基于Velocity DOM animation 实现的另外一个React动画库,你可以在 React Motion和它之间二选一。

相关阅读:

React+Redux技术栈核心要点解析(中篇)

React+Redux技术栈核心要点解析(下篇)

欢迎加入“CSDN前端开发者”群,与更多专家、技术同行进行热点、难点技术交流。请扫描以下二维码加群主微信,申请入群,务必注明「姓名+公司+职位」
图片描述

评论