乐趣区

关于前端:React学习初入React世界

React 简介

React 是 Facebook 在 2013 年开源 JavaScript 库。它把界面形象成一个个组件,通过组合这些组件,开发者能够失去功能丰富的页面。同时引入了 JSX 语法,使得复用组件变得容易,且构造清晰。并且有了组件这层的形象,代码和实在渲染指标拆散,除了能够在浏览器端渲染到 DOM 开发网页外,还能用于原生挪动利用的开发。

专一视图层

React 并不是残缺的 MVC/MVVM 框架,它专一于 View(视图)层解决方案。与模板引擎不同,React 又是一个包含 View 和 Controller 的库。

Virtual DOM

React 把实在 DOM 树转换成 JavaScript 对象树,也就是 Virtual DOM。每次数据更新,比照前后的 Virtual DOM,对发生变化的局部做批量更新,晋升性能。并且 Virtual DOM 能够不便地与其余平台集成,比方 react-native 就是基于 Virtual DOM 渲染原生控件的。

函数式编程

命令式编程是给计算机下命名,而函数式编程,对应的是申明式编程。申明式编程的实质是 lambda 演算,比方咱们要操作一个数组里的每个元素,返回一个新数组。咱们的做法是构建一个 f 函数 (规定) 作用在数组上,而后返回新数组。这样,计算能够被反复利用。

JSX 语法

JSX 是 react 为了不便 View 层组件化,承载构建 HTML 结构化页面职责的而创建的语言(语法)。

DOM 元素和组件元素

在 react 中创立的虚构元素能够分为两类,DOM 元素 (DOM element) 与组件元素(component element)。别离对应着原生 DOM 元素和自定义元素。

DOM 元素

当应用 JavaScript 来形容 Web 页面的 HTML 元素时,能够示意为纯正的 JSON 对象。例如,形容一个按钮

<button class="btn btn-blue">
  <em>Confirm</em>
</button>

->

{
  type: 'button',
  props: {
    className: 'btn btn-blue',
    children: [{
      type: 'em',
      props: {children: 'Confirm'}
    }]
  }
}

在 react 中,到处可见的元素并不是实在的实例,它们只是页面的形容对象。

组件元素

React 还能够自定义组件元素。
类比如下

const Button = ({color, text}) => {
  return {
    type: "button",
    props: {className: `btn btn-${color}`,
      children: [
        {
          type: "em",
          props: {children: text}
        }
      ]
    }
  };
};

Button 其实也能够作为元素而存在,办法名对应了元素类型,参数对应了元素属性。
这也是 React 的核心思想之一,咱们能够让 DOM 元素、组件元素嵌套、组合,最初用递归渲染的形式构建出齐全的 DOM 元素树。
然而这种写法不容易浏览和保护了,JSX 语法就应运而生了。

const Button = ({color, text}) => {<button className={`btn btn-${color}`}>
    <em>{text}</em>
  </button>;
};

JSX 将 HTML 语法间接退出到 JavaScript 代码中,再通过翻译器转换成纯 JavaScript 后再由浏览器执行。

JSX 根本语法

JSX 的官网定义是类 XML 语法的 ECMAScript 扩大。

XML 根本语法

应用类 XML 语法,咱们能够清晰地看到 DOM 树状后果及其属性。只不过它被包裹在 JavaScript 的办法中

const List = () => (
  <ul>
    <li>1</li>
    <li>2</li>
  </ul>
);

须要留神几点

  1. 定义标签时,只容许被一个标签包裹。
const c = () => (<span>1</span><span>2</span>);

会报错,最外层没有包裹,显然无奈转译成 React.createElement 办法调用

  1. 标签必须闭合,如<div></div><div />

元素类型

小写字母对应 DOM 元素,大写字母对应组件元素
此外,还有一些非凡的标签值得探讨,比方正文和 DOCTYPE 头
JSX 还是 JavsScript,仍然能够用简略的办法应用正文,在子元素地位应用正文要用{} 包起来。
对于罕用于判断浏览器版本的条件正文

<!--[if IE]>
  <p>work in IE</p>
<![endif]-->

须要用 JavaScript 判断来实现

{(!!window.ActiveXObject || 'ActiveXObject' in window) ?
  <p>work in IE</p> : ''
}

DOCTYPE 头是一个十分非凡的标记,个别会在 React 服务端渲染时用到。DOCTYPE 是没有闭合的,咱们无奈渲染它。常见的做法是结构一个保留 HTML 的变量,将 DOCTYPE 和整个 HTML 标签渲染后的后果串联起来。

元素属性

DOM 元素属性是标准规范属性,但 class 和 for 因为是关键字,由 className 和 htmlFor 代替。
组件元素属性是齐全自定义的属性,也能够了解为实现组件所须要的参数。个别采纳小驼峰写法。
此外还有一些特有的属性表白

  1. 省略 Boolean 属性值会导致 JSX 认为 bool 值设为了 true
<Checkbox checked={true} />

能够简写成

<Checkbox checked />
<Checkbox checked={false} />

能够省略为

<Checkbox />
  1. 开展属性
    应用 ES6 rest/spread 个性能够进步开发效率
const data = {name: "foo", value: "bar"};
const component = <Component {...data} />;
  1. 自定义 HTML 属性
    往 DOM 元素传入自定义属性,React 是不会渲染的
<div d="xxx">content</div>

须要应用 data- 前缀,这和 HTML 规范也是统一的

<div data-attr="xxx">content</div>

然而在自定义标签中,任意属性都是被反对的

<CustomComponent d="xxx" />
  1. JavaScript 属性表达式
<Person name={true ? 1 : 2} />
  1. HTML 本义
    React 会将所有要显示到 DOM 的字符串本义,避免 XSS。如&copy; 不会正确显示。
    能够通过以下办法解决 1. 间接应用 UTF-8 字符 2. 应用 Unicode 编码 3. 应用功数组组装<div>{['cc', <span>&copy;</span>, '2015']}</div> 4. 直接插入原始的 HTML

React 还提供了 dangerouslySetInnerHTML 属性。它防止了 React 转义字符,请在确定必要的状况下应用它

<div dangerouslySetInnerHTML={{__html: "cc &copy; 2015"}} />

React 组件

组件的演变

在 MVC 架构呈现之前,组件次要分为两种

  1. 广义上的组件,又称 UI 组件,次要围绕交互动作上的形象,利用 JavaScript 操作 DOM 构造或 style 款式来管制
  2. 狭义上的组件,带有业务含意和数据的 UI 组件组合。它更偏向于采纳分层的思维去解决
    对于 UI 组件,分为 3 局部:构造、款式和交互行为,对应于 HTML、CSS 和 JavaScript.

封装的基本思路就是面向对象思维。交互基本上以操作 DOM 为主。逻辑上是构造上哪里须要变,咱们就操作哪里。以下是几项标准规范组件的信息。

  1. 根本的封装性。只管说 JavaScript 没有真正面向对象的办法,然而咱们还是能够通过实例化的办法来制作对象。
  2. 简略的生命周期出现。如 contructor 和 destroy, 代表了组件的挂载和卸载过程。
  3. 明确的数据流动。这里的数据指的是调用组件的参数。一旦确定参数的值,就会解析传进来的参数,依据参数的不同作出不同的响应。

这个阶段,前端在利用级别没有过多简单的交互。传统组件的次要问题在于构造、款式与行为没有很好地联合,不同参数下的逻辑可能会导致不同的渲染逻辑,这时就会存在大量的 HTML 构造与 style 款式的拼装。逻辑一旦简单,开发及保护老本相当高。

于是分层思维引进了,呈现了 MVC 架构。View 只关怀怎么输入变量,所以就诞生了各种各样的模板语言。让模板自身承载逻辑,能够帮咱们解决 View 上的逻辑问题。对于组件来说,能够将拼装 HTML 的逻辑局部解耦进来,解决了数据与界面耦合的问题。

模板作为一个 DSL,也有其局限性。在 Angular 中,咱们看到了在 HTML 上定义指令的形式。

W3C 将相似的思维制订成了标准, 称为 Web Components。它通过定义 Custom Elements(自定义元素)的形式来对立组件。每个自定义元素能够定义本人对外提供的属性、办法, 还有事件,外部能够像写一个页面一样,专一于实现性能来实现对组件的封装。

Web Components 由 4 个组成部分:HTML Templates 定义了之前模板的概念,Custom Elements 定义了组件的展示模式,Shadow DOM 定义了组件的作用域范畴、能够囊括款式,HTML Imports 提出了新的引入形式。

事实上,它还是须要工夫的考验的。因为诸如如何包装在这套标准之上的框架,如何取得在浏览器端的全副反对,怎么与古代利用架构联合等等。但它却是开拓了一条罗马小道,通知咱们组件化能够这样去做。

React 组件的构建

React 的实质就是关怀元素的形成,React 组件即为组件元素。组件元素被形容成纯正的 JSON 对象,意味着能够应用办法或是类来构建。React 组件基本上由 3 个局部组成 - 属性 (props)、状态(state) 以及生命周期办法。

React 组件能够接管参数,也可能有本身状态。一旦接管到的参数或本身状态有所扭转,React 组件就会执行相应的生命周期办法,最初渲染。

1.React 与 Web Components
从 React 组件上看,它与 Web Components 传播的理念是统一的,但两者的实现形式不同:

  1. React 自定义元素是库本人构建的,与 Web Components 标准并不通用;
  2. React 渲染过程包含了模板的概念,即 JSX
  3. React 组件的实现均在办法与类中,因而能够做到互相隔离,但不包含款式。
  4. React 援用形式遵循 ES6 module 规范

React 在纯 JavaScript 高低了时间,将 HTML 构造彻底引入到 JavaScript 中。这种做法褒贬不一,但无效地解决了组件所要解决的问题之一。

2.React 组件的构建办法
React 组件基本上由组件的构建形式、组件内的属性状态与生命周期办法组成。

React 组件构建上提供了 3 种不同的办法:React.createClass、ES6 classes 和无状态函数。

React.createClass
用 React.createClass 构建组件是 React 最传统、也是兼容性最好的办法。

const Button = React.createClass({getDefaultProps() {
    return {
      color: "blue",
      text: "Confirm"
    };
  },

  render() {const { color, text} = this.props;

    return (<button className={`btn btn-${color}`}>
        <em>{text}</em>
      </button>
    );
  }
});

ES6 classes
ES6 classes 的写法是通过 ES6 规范的类语法的形式来构建办法:

import React, {Component} from "react";

class Button extends Component {contructor(props) {super(props);
  }

  static defaultProps = {
    color: "blue",
    text: "Confirm"
  };

  render() {const { color, text} = this.props;

    return (<button className={`btn btn-${color}`}>
        <em>{text}</em>
      </button>
    );
  }
}

与 createClass 的后果雷同的是,调用类实现的组件会创立实例对象。

咱们很容易联想到组件形象过程中也能够应用继承的思路。在理论利用中,咱们极少让子类去继承性能组件。继承牵一发而动全身。在 React 组件开发中,罕用的形式是将组件拆分到正当的粒度,用组合的形式合成业务组件。

阐明:React 的所有组件都继承自顶层类 React.Component。它的定义十分简洁,只是初始化了 React.Component 办法,申明了 props、context、refs 等,并在原型上定义了 setState 和 foreUpdate 办法。外部初始化的生命周期办法与 createClass 形式应用的是同一个办法创立的。

无状态函数
应用无状态函数构建的组件称为无状态组件

function Button({color = "blue", text = "Confirm"}) {
  return (<button className={`btn btn-${color}`}>
      <em>{text}</em>
    </button>
  );
}

无状态组件只传入 props 和 context 两个参数;也就是说,它不存在 state,也没有生命周期办法,组件自身即下面两种 React 组件构建办法中的 render。不过,像 propTypes 和 defaultProps 还是能够通过向办法设置动态属性来实现的。

无状态组件不像上述两种办法在调用时会创立新实例,它创立时始终保持了一个实例,防止了不必要的检查和内存调配。

React 数据流

在 React 中,数据是自顶向下单向流动的,即从父组件到子组件。

state 与 props 是组件中最重要的概念。如果顶层组件初始化 props,那么 React 会向下遍历整棵组件树,从新尝试渲染所有相干的子组件。state 只关怀组件本人外部的状态,这些状态只能在组件内扭转。把组件看成一个函数,props 就是它的参数,外部由 state 作为函数的外部参数,返回一个 Virtual DOM 的实现。

state

在 React 中,state 为组件外部状态。当组件外部应用 setState 办法时,该组件会尝试从新渲染。

值得注意的,setState 是一个异步办法,一个生命周期内所有的 setState 办法会合并操作。

咱们思考一个惯例的 Tabs 组件,对于 activeIndex 作为 state,就有两种不同的视角。

  1. 在外部更新。当咱们切换 tab 标签时,能够看作是组件外部的交互行为,被抉择后通过回调函数返回具体抉择的索引。
  2. 在内部更新。当咱们切换 tab 标签时,能够看作是组件内部在传入具体的索引,而组件就像“木偶”一样被操控着。
    这两种情景在 React 组件的设计中十分常见,咱们别离称为智能组件和木偶组件

当然,实现组件时,能够同时思考兼容这两种

constructor(props) {super(props);

  const currProps = this.props;

  let activeIndex = 0;
  // 来源于须要内部更新的
  if (activeIndex in currProps) {
    activeIndex = currProps.activeIndex;
    // 来源于应用外部更新的
  } else if ('defaultActiveIndex' in currProps) {activeIndex = currProps.defaultActiveIndex;}

  this.state = {
    activeIndex,
    prevIndex: activeIndex,
  };
}

props

props 是 React 用来让组件之间互相联系的一种机制,艰深地说就像办法的参数一样。

props 的传递过程,对于 React 组件来说十分直观。React 的单向数据流,次要的流动管道就是 props。props 自身是不可变的。组件的 props 肯定来自于默认属性或通过父组件传递而来。

React 为 props 提供了默认配置,通过 defaultProps 动态变量的形式来定义。

static defaultProps = {
  classPrefix: 'tabs',
  onChange: () => {},
};

子组件 prop

在 React 中有一个重要且内置的 props——children,它代表组件的子组件汇合。

实现的基本思路以 TabContent 组件渲染 TabPane 子组件汇合为例来讲

getTabPanes() {const { classPrefix, activeIndex, panels, isActive} = this.props;

  return React.Children.map(panels, (child) => {if (!child) {return;}

    const order = parseInt(child.props.order, 10);

    return React.cloneElement(child, {
      classPrefix,
      isActive,
      children: child.props.children,
      key: `tabpane-${order}`,
    });
  });
}

它是通过 React.Children.map 办法遍历子组件,同时利用 React 的 cloneElement 办法克隆到 TabPane 组件,最初返回这个 TabPane 组件汇合。

React.Children 是 React 官网提供的一系列操作 children 的办法。它提供诸如 map、forEach、count 等实用函数。
应用 getTabPanes

render () {return (<div>{this.getTabPanes()}</div>);
}

如果咱们把 render 办法中的 this.getTabPanes 办法中对子组件的遍历间接放进去

render() {return (<div>{React.Children.map(this.props.children, (child) => {...})}</div>)
}

这种调用形式称为 Dynamic Children(动静子组件)。

组件 props

也能够将子组件以 props 的模式传递。个别咱们会用这种办法让开发者定义组件的某一个 prop,让其具备多种类型,来做到简略配置和自定义配置组合在一起的成果。

用 function prop 与父组件通信

this.props.onChange(activeIndex, prevIndex)

触发了 onChange prop 回调函数给父组件必要的值。

propTypes

propTypes 用于标准 props 的类型与必须的状态。它会在开发环境下,对组件的 prop 值的类型作查看。

static propTypes = {classPrefix: React.PropTypes.string,}

propTypes 有很多类型反对,不仅有根本类型,还包含枚举和自定义类型。

React 生命周期

挂载和卸载过程

1. 组件的挂载
这个过程次要做组件状态的初始化,咱们举荐以上面例子为模板写初始化组件:

import React, {Component, PropTypes} from 'react';

class App extends Component {
  static propTypes = {// ...}

  static defaultProps = {// ...}

  constructor(props) {super(props)

    this.state = {// ...}
  }

  componentWillMount() {// ...}

  componentDidMount() {// ...}

  render () {return <div>This is a demo</div>}
}

如果咱们在 componentWillMount 中执行 setState 办法,组件会更新 state,但组件只渲染一次。因而,这是无意义的执行,齐全能够放在 constructor 初始化 state 中。
如果咱们在 componentDidMount 中执行 setState 办法,组件会再次更新,不过在初始化过程就渲染了两次组件,这并不是一次坏事。但理论状况,有一些场景必须这么做,比方须要获取组件的地位。

  1. 组件的卸载

componentWillUnmount,咱们经常会执行一些清理办法,比方事件回收、革除定时器。

数据更新过程

更新过程指的是父组件向下传递 props 或组件本身执行 setState 办法时产生的一系列更新动作。

import React, {Component, PropTypes} from 'react'

class App extends Component {componentWillReceiveProps(nextProps) {// this.setState({})
  }

  shouldComponentUpdate(nextProps, nextState) {// return true}

  componentWillUpdate(nextProps, nextState) { }

  componentDidUpdate(prevProps, prevState) { }

  render() {}
}

如果组件本身的 state 更新了,会顺次执行 shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate

shouldComponentUpdate 接管须要更新的 props 和 state,让开发者减少判断逻辑,不须要更新办法最终返回 false 即可,这是性能优化的伎俩之一。

无状态组件是没有生命周期办法的,这也意味着它没有 shouldComponentUpdate。渲染该类组件,每次都会从新渲染。

componentWillUpdate 办法提供的是须要更新的 props 和 state,而 componentDidUpdate 提供更新前的 props 和 state。

留神不能在 componentWillUpdate 执行 setState 办法,会导致循环执行 render。

如果组件是由父组件更新 props 而更新的,那么在 shouldComponentUpdate 之前会先执行 componentWillRecieveProps 办法。此办法能够作为 React 在 props 传入后,渲染之前 setState 的机会,在此办法中调用 setState 是不会二次渲染的。

componentWillReceiveProps(nextProps) {if ('activeIndex' in nextProps) {
    this.setState({activeIndex: nextProps.activeIndex})
  }
}

React 与 DOM

ReactDOM

ReactDOM 中的 API 非常少,只有 findDOMNode、unmountComponentAtNode 和 render。
1.findDOMNode
Reactz 提供的获取 DOM 元素的办法有两种,其中一种就是 ReactDOM 提供的 findDOMNode:

DOMElement findDOMNode(ReactComponent component)

当组件被渲染到 DOM 后,findDOMNode 返回该 React 组件实例相应的 DOM 节点。它能够用于获取表单的 value 以及用于 DOM 的测量。

class App extends Component {componentDidMount() {const dom = ReactDOM.findDOMNode(this)
  }

  render() {}
}

render

ReactComponent render(
  ReactElement element,
  DOMElement container,
  [function callback]
)

该办法把元素挂载到 container 中,并且返回 element 的实例(即 refs 援用)。如果是无状态组件,render 会返回 null。当组件装载结束时,callback 被调用。

与 render 相同,React 还提供了一个很少应用的 unmountComponentAtNode 办法来进行卸载操作。

ReactDOM 的不稳固办法

unstable_renderSubtreeIntoContainer。它能够更新组件到传入的 DOM 节点。它与 render 办法相比,区别在于是否传入父节点。

另一个 ReactDOM 中的不稳固办法 unstable_batchedUpdates 是对于 setState 更新策略的。

refs

它是 React 组件中十分非凡的 prop,能够附加到任何一个组件上。组件被调用时会新建一个该组件的实例,而 refs 就会指向这个实例。

findDOMNode 和 refs 都无奈用于无状态组件中,无状态组件挂载只是办法调用,没有新建实例。

React 之外的 DOM 操作

调用 HTML5 Audio/Video 的 play 办法和 input 的 focus 办法,React 就无能为力了,须要应用相应的 DOM 办法来实现。

还有组件以外区域(个别指 document、body)的事件绑定、DOM 的尺寸计算。

参考

《深刻 React 技术栈》

退出移动版