写在前面:从5月份到现在因为工作原因、很久没有更新博客文章了。工作上最近慢慢稳定下来、开始继续更新文章;新的技术栈从React开始,后面会慢慢增加文章更新量;有写的不对的、不好的地方小伙伴们可以直接指出来。

1、什么是React?

React是由Facebook(脸书)开源的一个进行创建用户界面的一款JavaScript库,如今已应用于Facebook及旗下的Instagram应用。React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。React与庞大的AngularJS不同的地方在于它只专注于MVC框架中的V,即视图;这点使得React很容易与开发者已有的开发栈进行融合。React在使用的时候,应该从UI出发,抽象出不同的组件,继而将它们拼装起来;这点顺应了Web开发组件化的趋势。

React不依赖其他任何的库,因此开发中,可以与任何其他的库集成使用,包括Jquery、Backbone等。它可以在浏览器端运行,也可以通过nodejs在服务端渲染。React的思想非常独特,性能出众,可以写出重复代码少,逻辑清晰的前端代码。React的语法是jsx,通过使用这种语法,可以在react代码中直接混合使用js和html来编写代码,这样代码的逻辑就非常清晰,当然也意味着,需要将jsx代码编译成普通的javascript代码,才能在浏览器中运行,这个过程根据实际项目情况,可以选择多种不同的思路,或者在服务器端通过webpack进行编译。

React官网地址:https://reactjs.org/

React中文文档:https://zh-hans.reactjs.org/docs/introducing-jsx.html

Github地址:https://github.com/facebook/react/

2、React核心概念

React 的核心思想是:封装组件。各个组件维护自己的状态和UI,当状态变更,自动重新渲染整个组件。基于这种方式的一个直观感受就是我们不再需要不厌其烦地来回查找某个 DOM元素,然后操作 DOM 去更改 UI。React 大体包含下面这些概念:

  • JSX
  • 组件
  • Props & State
  • 组件生命周期
  • 组件API

下面我们来一个个看看这些核心概念都是什么意思。

2.1、JSX

JSX的全称是 Javascript and XML,React发明了JSX,它是一种可以在JS中编写XML的语言。React 使用 JSX 来替代常规的 JavaScript。JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。我们不需要一定使用 JSX,但它有以下优点:

  • JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
  • 它是类型安全的,在编译过程中就能发现错误。
  • 使用 JSX 编写模板更加简单快速。

JSX更像一种模板,类似于Vue中的 template。这种看起来可能有些奇怪的标签语法既不是字符串也不是 HTML。它被称为 JSX, 一种 JavaScript 的语法扩展。 我们推荐在 React 中使用 JSX 来描述用户界面。JSX 是在 JavaScript 内部实现的,我们知道元素是构成 React 应用的最小单位,JSX 就是用来声明 React 当中的元素。与浏览器的 DOM 元素不同,React 当中的元素事实上是普通的对象,React DOM 可以确保 浏览器 DOM 的数据内容与 React 元素保持一致。假设我们有一个React组件,它只渲染一个<h1>标签。JSX允许我们以种非常类似HTML的方式声明这个元素:

class HelloWorld extends React.Component { 
    render() { 
        return (
            <h1 className="title">Hello World</h1> 
        ) 
    } 
}

HelloWorld组件中的render()函数看起来返回的是HTML,但实际上这是JSX。JSX在运行时被转换为常规则JavaScript。翻译后的组件看起来是这样的:

class HelloWorld extends React.Component {
    render() { return (
        React.createElement(
            'h1', 
            {className: 'title'}, 
            'Hello World' 
        ) 
    ) 
   } 
}

虽然JSX看起来像HTML,但实际上它只是一种编写React.createElement()声明的更简洁的方法。当组件渲染时,它输出一个React元素树或该组件输出的HTML元素的虚拟DOM。然后,React将根据这个React元素表示确定对实际DOM进行哪些更改。HelloWorld组件渲染出来的React的HTML看起来像下面这样:

<h1 class='title'>Hello World</h1>

讲了这么多、那么我们为什么要使用JSX呢?React认为渲染逻辑本质上与其他UI逻辑内在耦合,比如,在UI中需要绑定处理事件、在某些时刻状态发生变化时需要通知到UI,以及需要在UI中展示准备好的数据。React并没有采用将标记与逻辑进行分离到不同文件这种人为地分离方式,而是通过将二者共同存放在称之为组件的松散耦合单元之中,来实现“关注点分离”。React不强制要求使用JSX,但大多数人发现,在JavaScript代码中将JSX和UI放在一起时,会在视觉上有辅助作用。它还可以使React显示更多有用的错误和警告消息。

2.2、组件

那么什么是组件呢?组件是一个具备UI 描述UI 数据的完整体。它的数据结构是类或函数,返回 React 元素。 简单来说,可以把组件看成是一个函数,输入的是 props 和 state,输出的是组件的 UI(UI 描述)。React 正是通过组件化的思想,将界面拆分成一个个可以复用的模块,每一个模块就是一个 React 组件。React 有两种组件,一种是函数组件,另一种是 class 组件。顾名思义,函数组件使用 function 定义的组件,class 组件是用 class 定义的组件。

函数式组件,最简单的定义组件的方法是写一个 JavaScript 函数:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

这个函数是一个有效的 React 组件,因为它接收一个 props 参数, 并返回一个 React 元素。 我们把此类组件称为”函数式(Functional)“组件, 因为从字面上看来它就是一个 JavaScript 函数。当然、你也可以用一个 ES6 的 class来定义一个组件:

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

注:如果你想写的组件只包含一个 render 方法,并且不包含 state,那么使用函数组件就会更简单。其次、上面两个组件从 React 的角度来看是等效的。这里我们还需要注意一点组件名称必须以大写字母开头,React 会将以小写字母开头的组件视为原生 DOM 标签。

每个组件都有自己的属性(props)和状态(state);组件的 props 是只读的。React 组件必须要像纯函数一样保护它们的 props 不被修改。React组件还有一些其他的概念、比如渲染组件、组合组件、提取组件。各位小伙伴对这些组件有兴趣的、可以自行去查看管网文档、这里就不再详细描述。

2.3、Props & State

前面我们提到了props和state、React 中的 props(“properties” 的缩写)和 state 都是普通的 JavaScript 对象。它们都是用来保存信息的,这些信息可以控制组件的渲染输出,而它们的几个重要的不同点就是:

1、props 是传递给组件的(类似于函数的形参),而 state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量)。props 是不可修改的,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。由于 props 是传入的,并且它们不能更改,因此我们可以将任何仅使用 props 的 React 组件视为 pureComponent,也就是说,在相同的输入下,它将始终呈现相同的输出。

2、state 是在组件中创建的,一般在 constructor 中初始化 state。state 是多变的、可以修改,一般通过 setState 被修改,并且一般是异步更新的。

通俗点讲,state 和 props 主要的区别在于 props 是不可变的,而 state 可以根据与用户交互来改变。这就是为什么有些容器组件需要定义 state 来更新和修改数据,而子组件只能通过 props 来传递数据。

我们通过下面的例子来看看props的只读性:

function sum(a, b) {
  return a + b;
}

上面的函数被称为纯函数,因为该函数不会尝试更改入参,且多次调用下相同的入参始终返回相同的结果。相反,下面这个函数则不是纯函数,因为它更改了自己的入参:

function withdraw(account, amount) {
  account.total -= amount;
}

React 非常灵活,但它也有一个严格的规则:所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

当然,应用程序的 UI 是动态的,并会伴随着时间的推移而变化。这里还有一个重要的概念,我们称之为 “state”。在不违反上述规则的情况下,state 允许 React 组件随用户操作、网络响应或者其他变化而动态更改输出内容。

React 把组件看成是一个状态机(State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。以下实例创建一个名称扩展为 React.Component 的 ES6 类,在 render() 方法中使用 this.state 来修改当前的时间。添加一个类构造函数来初始化状态 this.state,类组件应始终使用 props 调用基础构造函数:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>现在是 {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('example')
);

上面的例子中,Clock计时器是不会自动更新的。接下来,我们将使Clock设置自己的计时器并每秒更新一次。

2.4、组件生命周期

组件的生命周期可分成三个状态:

  • Mounting(挂载):已插入真实 DOM
  • Updating(更新):正在被重新渲染
  • Unmounting(卸载):已移出真实 DOM

img

挂载

当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:

  • constructor(): 在 React 组件挂载之前,会调用它的构造函数。
  • getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
  • render(): render() 方法是 class 组件中唯一必须实现的方法。
  • componentDidMount(): 在组件挂载后(插入 DOM 树中)立即调用。

注:render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。

更新

每当组件的 state 或 props 发生变化时,组件就会更新。当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:

  • getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。
  • shouldComponentUpdate():当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。
  • render(): render() 方法是 class 组件中唯一必须实现的方法。
  • getSnapshotBeforeUpdate(): 在最近一次渲染输出(提交到 DOM 节点)之前调用。
  • componentDidUpdate(): 在更新后会被立即调用。

注:render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。

卸载

当组件从 DOM 中移除时会调用如下方法:

  • componentWillUnmount(): 在组件卸载及销毁之前直接调用。

在具有许多组件的应用程序中,在销毁时释放组件所占用的资源非常重要。每当 Clock 组件第一次加载到 DOM 中的时候,我们都想生成定时器,这在 React 中被称为挂载。同样,每当 Clock 生成的这个 DOM 被移除的时候,我们也会想要清除定时器,这在 React 中被称为卸载。我们可以在组件类上声明特殊的方法,当组件挂载或卸载时,来运行一些代码:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>现在是 {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('example')
);

实例解析: componentDidMount()componentWillUnmount() 方法被称作生命周期钩子。在组件输出到 DOM 后会执行 componentDidMount() 钩子,我们就可以在这个钩子上设置一个定时器。this.timerID 为定时器的 ID,我们可以在 componentWillUnmount() 钩子中卸载定时器。

代码执行顺序:

1、当 <Clock /> 被传递给 ReactDOM.render() 时,React 调用 Clock 组件的构造函数。 由于 Clock 需要显示当前时间,所以使用包含当前时间的对象来初始化 this.state 。 我们稍后会更新此状态。

2、React 然后调用 Clock 组件的 render() 方法。这是 React 了解屏幕上应该显示什么内容,然后 React 更新 DOM 以匹配 Clock 的渲染输出。

3、当 Clock 的输出插入到 DOM 中时,React 调用 componentDidMount() 生命周期钩子。 在其中,Clock 组件要求浏览器设置一个定时器,每秒钟调用一次 tick()

4、浏览器每秒钟调用 tick() 方法。 在其中,Clock 组件通过使用包含当前时间的对象调用 setState() 来调度UI更新。 通过调用 setState() ,React 知道状态已经改变,并再次调用 render() 方法来确定屏幕上应当显示什么。 这一次,render() 方法中的 this.state.date 将不同,所以渲染输出将包含更新的时间,并相应地更新 DOM。

5、一旦 Clock 组件被从 DOM 中移除,React 会调用 componentWillUnmount() 这个钩子函数,定时器也就会被清除。

这里我们需要注意的是父组件或子组件都不能知道某个组件是有状态还是无状态,并且它们不应该关心某组件是被定义为一个函数还是一个类。这就是为什么状态通常被称为局部或封装。 除了拥有并设置它的组件外,其它组件不可访问。我们来看下面这个实例:

function FormattedDate(props) {
  return <h2>现在是 {props.date.toLocaleTimeString()}.</h2>;
}

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <FormattedDate date={this.state.date} />
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('example')
);

在上面这个实例中, FormattedDate 组件将在其属性中接收到 date 值,并且不知道它是来自 Clock 状态、还是来自 Clock 的属性。这通常被称为自顶向下或单向数据流。 任何状态始终由某些特定组件所有,并且从该状态导出的任何数据或 UI 只能影响树中下方的组件。如果你想象一个组件树作为属性的瀑布,每个组件的状态就像一个额外的水源,它连接在一个任意点,但也流下来。为了表明所有组件都是真正隔离的,我们可以创建一个 App 组件,它渲染三个Clock:

function FormattedDate(props) {
  return <h2>现在是 {props.date.toLocaleTimeString()}.</h2>;
}

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <FormattedDate date={this.state.date} />
      </div>
    );
  }
}

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('example'));

以上实例中每个 Clock 组件都建立了自己的定时器并且独立更新。在 React 应用程序中,组件是有状态还是无状态被认为是可能随时间而变化的组件的实现细节。我们可以在有状态组件中使用无状态组件,也可以在无状态组件中使用有状态组件。

2.5、组件API

React之所以快,是因为它不直接操作DOM。React将DOM结构存储在内存中,然后同render()的返回内容进行比较,计算出需要改动的地方,最后才反映到DOM中。此外,React实现了一套完整的事件合成机制,能够保持事件冒泡的一致性,跨浏览器执行。甚至可以在IE8中使用HTML5的事件。大部分情况下,我们都是在构建React的组件,也就是操作虚拟DOM。但是有时候我们需要访问底层的API,可能或通过使用第三方的插件来实现我们的功能,如jQuery。当然、React也提供了接口让我们操作底层API。React组件API提供了7个方法供我们使用:

  • 设置状态:setState
  • 替换状态:replaceState
  • 设置属性:setProps
  • 替换属性:replaceProps
  • 强制更新:forceUpdate
  • 获取DOM节点:findDOMNode
  • 判断组件挂载状态:isMounted

这里我们重点讲一下设置状态:setState和强制更新:forceUpdate。

setState(object nextState[,function callback])

注:合并nextState 和当前 state。这是在事件处理函数中和请求回调函数中触发 UI 更新的主要方法。另外,也支持可选的回调函数,该函数在 setState 执行完毕并且组件重新渲染完成之后调用。

首先我们不能在组件内部通过this.state修改状态,因为该状态会在调用setState()后被替换。setState()并不会立即改变this.state,而是创建一个即将处理的state。setState()并不一定是同步的,为了提升性能React会批量执行state和DOM渲染。setState()总是会触发一次组件重绘,除非在shouldComponentUpdate()中实现了一些条件渲染逻辑。如果使用可变的对象,但是又不能在 shouldComponentUpdate() 中实现这种逻辑,仅在新 state 和之前的 state存在差异的时候调用 setState() 可以避免不必要的重新渲染。

class Counter extends React.Component{
  constructor(props) {
      super(props);
      this.state = {clickCount: 0};
      this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(function(state) {
      return {clickCount: state.clickCount + 1};
    });
  }
  render () {
    return (<h2 onClick={this.handleClick}>点我!点击次数为: {this.state.clickCount}</h2>);
  }
}
ReactDOM.render(
  <Counter />,
  document.getElementById('example')
);

注:在上面的实例中,我们可以通过点击 h2 标签来使得点击计数器加 1。

forceUpdate([function callback])

如果 render() 方法从 this.props 或者 this.state 之外的地方读取数据,你需要通过调用 forceUpdate() 告诉 React什么时候需要再次运行 render()。如果直接改变了this.state,也需要调用 forceUpdate()。调用 forceUpdate() 将会导致 render() 方法在相应的组件上被调用,并且子级组件也会调用自己的 render(),但是如果标记改变了,那么 React仅会更新 DOM。通常情况下,应该尽量避免所有使用 forceUpdate() 的情况,在 render() 中仅从this.props 和 this.state 中读取数据。这会使应用大大简化,并且更加高效。

3、React和Vue的区别

React 的思路是 HTML in JavaScript 也可以说是 All in JavaScript,通过 JavaScript 来生成 HTML,所以设计了 JSX 语法,还有通过 JS 来操作 CSS,社区的styled-component、JSS等。而Vue 是把 HTML,CSS,JavaScript 组合到一起,用各自的处理方式,Vue 有单文件组件,可以把 HTML、CSS、JS 写到一个文件中,HTML 提供了模板引擎来处理。

React 整体是函数式的思想,在 React 中是单向数据流,推崇结合 immutable 来实现数据不可变。而 Vue 的思想是响应式的,也就是基于是数据可变的,通过对每一个属性建立 Watcher 来监听,当属性变化的时候,响应式的更新对应的虚拟 DOM。如上,所以 React 的性能优化需要手动去做,而Vue的性能优化是自动的,但是Vue的响应式机制也有问题,就是当 state 特别多的时候,Watcher 会很多,会导致卡顿。

React 与 Vue 存在很多共同点,例如他们都是 JavaScript 的 UI 框架,专注于创造前端的富应用。不同于早期的 JavaScript 框架“功能齐全”,Reat 与 Vue 只有框架的骨架,其他的功能如路由、状态管理等是框架分离的组件。

React

  • 灵活性和响应性:它提供最大的灵活性和响应能力。
  • 丰富的JavaScript库:来自世界各地的贡献者正在努力添加更多功能。
  • 可扩展性:由于其灵活的结构和可扩展性,React已被证明对大型应用程序更好。
  • 不断发展: React得到了Facebook专业开发人员的支持,他们不断寻找改进方法。
  • Web或移动平台: React提供React Native平台,可通过相同的React组件模型为iOS和Android开发本机呈现的应用程序。

Vue

  • 易于使用: Vue.js包含基于HTML的标准模板,可以更轻松地使用和修改现有应用程序。
  • 更顺畅的集成:无论是单页应用程序还是复杂的Web界面,Vue.js都可以更平滑地集成更小的部件,而不会对整个系统产生任何影响。
  • 更好的性能,更小的尺寸:它占用更少的空间,并且往往比其他框架提供更好的性能。
  • 精心编写的文档:通过详细的文档提供简单的学习曲线,无需额外的知识; HTML和JavaScript将完成工作。
  • 适应性:整体声音设计和架构使其成为一种流行的JavaScript框架。它提供无障碍的迁移,简单有效的结构和可重用的模板。

如上所说的 Vue 的响应式机制也有问题,当 state 特别多的时候,Watcher 会很多,会导致卡顿,所以大型应用(状态特别多的)一般用 React,更加可控。可对于易用性来说,VUE 是更容易上手的,对于项目来说新人更容易接手。

使用 Reac 的公司:Facebook,Instagram,Netflix,纽约时报,雅虎,WhatsApp,Codecademy,Dropbox,Airbnb,Asana,微软等。

使用 Vue 的公司:Facebook,Netflix,Adobe,Grammarly,Behance,小米,阿里巴巴,Codeship,Gitlab和Laracasts等。

所以,技术没有哪个更好或者是更优秀,只要适合自己的才是最合适的。