React基础篇学习

到今天为止, 使用 react 已经一年了, 现在整理一下入门时的一些重要知识, 帮助想要学习 react 的同学们理解某些内容。

React 元素

React 元素,它是 React 中最小基本单位,我们可以使用 JSX 语法轻松地创建一个 React 元素:

const element = <div className="demo">Hello Everyone!</div>;

React 元素是虚拟 DOM 元素,并不是真实的 DOM 元素,它仅仅是 javascript 的 普通对象,所以也没办法直接调用 DOM 原生的 API。

如果访问 element.style 会得到 undefined.

上面的 JSX 转译后的对象大概是这样的:

{
  "type": "div",
  "key": null,
  "ref": null,
  "props": {
    "children": "Hello Everyone!",
    "className": "demo"
  },
  "_owner": null,
  "_store": {}
}

只有在这个元素渲染被完成后,才能通过选择器的方式获取它对应的 DOM 元素。不过要尽量避免 DOM 操作,即便要进行 DOM 操作,也应该使用 React 提供的接口reffindDOMNode()

除了使用JSX语法,我们还可以使用React.createElement()React.cloneElement()来构建 React 元素。

React.createElement()

JSX 语法就是用React.createElement()来构建 React 元素的。它接受三个参数,第一个参数可以是一个标签名。如divspan,或者 ReactClass组件名。第二个参数为传入的属性。第三个以及之后的参数,皆作为组件的子组件。

React.createElement(type, [props], [...children]);

** React.cloneElement() **

该方法与上面的方法相似, 不同的是它传入的第一个参数是React元素, 而不是html标签名或组件, 新添加的属性会并入原有的属性, 传入到返回的新元素中,原来的子元素会被替换掉. 该方法第一个参数若是小写的话会被当作 html 标签

在 React 中, 组件是用于分离关注点的, 而不是被当作模版或处理显示逻辑的, 组件是一个函数, 这点与元素不同, 元素是个 js 对象, typeof 得到的是 object, 而组件 typeof 出来的是 function.

React 中有 3 种构建组件的方式. React.createClass , ES6 class , 无状态函数.

React.createClass()

var Greeting = React.createClass({
  render: function () {
    return <h1>Hello, {this.ptops.name}</h1>;
  },
});

ES6 class

实现仍是调用 React.createClass(), 其生命周期和自动绑定方式与 React.createClass() 略有不同.

无状态函数

无状态函数是使用函数构建的无状态组件,无状态组件传入 props 和 context 两个参数,它没有 state,除了 render(),没有其它生命周期方法。

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

React.createClass()ES6 class 构建的组件的数据结构是类,无状态组件 数据结构是函数,它们在 React 中被视为是一样的。

元素与组件的区别

组件是用来生成元素的, 元素是组件实例化的描述。元素数据结构是普通对象,而组件数据结构是类或纯函数。除此之外,还有几点区别要注意:

** this.props.children **

在 JSX 中,被元素嵌套的元素会以属性 children 的方式传入该元素的组件。当仅嵌套一个元素时,children 是一个 React 元素,当嵌套多个元素时,children 是一个 React 元素的数组。可以直接把 children 写入 JSX 中,但如果要给它们传入新属性,就要用到 React.cloneElement()来构建新的元素。

render() {
    let Child = this.props.children;
    return <div><Child tip={'some-value'} /></div>
}

这种写法是行不通的, 因为 this.props.children 是一个 React 元素, 并不是组件, 正确的方法是:

render() {
    var Child = this.props.children;
    return <div>{React.cloneElement(Child, {tip: 'this is right'})}</div>;
}

** 自定义组件 **

以属性传入组件的是 React 元素, 并非 React 组件

<Nav sub={<SubNav />} />

<Nav sub={SubNav}>

我们可以认为 SubNav 是 React 组件, <SubNav /> 是 React 元素.

Render

  1. 一旦组件的 this.state 或者 this.props 的值发生变化, 当前的组件将会被重新绘制, 即 render。 这里重新绘制并不是重建, this.getInitialState, this.componentWillMountthis.componentDidMount 不会被在此调用。

  2. 通过变更 this.setState 引起重绘是当前组件引起重绘的主要手段,还有一个手段是 this.forceUpdate()。而通过为子组件设定新的 props 的方式,是父组件 引起子组件重绘的主要方法。

  3. 检测 this.state 或者 this.props 的不同是通过“深度比较”来完成的,也就是说,只要整个对象树的任何一个“叶子”的值变化,就将触发“重绘”。所以你需要密切注意每一个 Component 的 state 和 props 到底绑定了多大范围的数据,避免不必要的“重绘”。

  4. 你可以通过重载 this.shouldComponentUpdate 替代 React 的深度比较算法。但是,只有当你真的理得清楚的时候才使用这种方法。

Component Mount

  1. 组件在第一次被绘制前被 Mount,之前会执行 this.getInitialStatethis.componentWillMount 等一系列的类成员初始化方法。

  2. 但是, 重绘 不会引发 Re-mount(你在 父组件 为 子组件 设定新的 props,只会引起 子组件 的 Re-render,不会引起 子组件 的 Re-mount(重新创建))。

  3. React 仅在两种情况下会 Re-mount 组件。一种是通过 React.render 重新绘制了整个 组件树; 还有一种就是 父组件 为 子组件 设定了 key property,且当 key property 变化了的时候,老的 子组件 被卸载 并销毁, 而新的子组件被重新创建、初始化和 Mount

JSX 是什么

它是一种 JavaScript 语法扩展,在 React 中可以方便地用来描述 UI。

JSX 基本语法规则:

JSX 本身就和 XML 语法类似,可以定义属性以及子元素。唯一特殊的是可以用大括号来加入 JavaScript 表达式。遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析。对于相邻的两个同级元素必须要有一个外层标签来包裹, 一个 jsx 元素总是被一个最外层的标签包裹, jsx 标签是严格封闭的;

// 这样写是不正确的
let ele = (<span>hello</span><span>world</span>);
// 这样写是可以的
let ele = [<div><span>hello</span><span>world</span></div>];
let ele = [<span>hello</span>, <span>world</span>];     // 此时的ele并不是react元素

JSX 的使用方法

我们在使用尖括号来创建 jsx 元素, 如 <div /> 其实是调用了 React.createElement() 方法, 也正因为这样在有 jsx 出现的地方, 都需要引入 React,

<Div />, <div />, <shuai />

  • 如果是大写字母开头的将会被解析成变量, 该变量必须是字符串或者是个 function 不然会报错, 如果是字符串, 而且开头也大写了, 将会被解析成对应的小写的 html 标签, 该标签可以不是 html 的标准标签;
  • 若是表达式将会报错, 这一点与 React.createElement() 有所不同, 后者可以接受一个表达式做为其 type ,
  • 如果是小写字母开头的将会解析成标签, 尽管 html5 中没有这个标签也会被解析出来, 这也是为什么自定义的组件名必须要大写字母开头;
let obj = {
    a: 'div',
    b: 'Div',
    c: 'Hello',
    d: function(){return <div />;}
};
let d = function() {return <div />};
let Div = function() {return <div />};
window.element = <obj['a'] />;      // error, 标签名不能是js表达式
window.element = <(obj.a) />;       // error, 标签名不能是js表达式
window.element = <obj.b />;         // right, <div></div>
window.element = <obj.d />;         // <div></div>
window.element = <d />;             // <d></d>
window.element = <Div />;           // <div></div>

JSX 中的 if-else

// 在jsx标签属性中使用if:
<div msg' }}>Hello World!</div>
// babel编译之后:
React.createElement(
    "div",
    {id: if (true) { 'msg' }},
    "Hello World!"
)
// 在jsx标签之间使用if
<div>{if(true) 'Hello World!'}</div>
// babel编译之后:
React.createElement(
    "div",
    null,
    if(true) 'Hello World!'
);

不要使用 && || 给属性赋值

JSX 中使用 js

  1. 在 jsx 中使用变量: 这个情况很常见;
  2. jsx 中使用函数, 一般在标签之间使用函数, 而且必须执行完返回一个 jsx元素 或者 原始类型的值, 或者返回一个(合法的 jsx 元素, 原始类型的值)的集合, [

    , 'Right']是合法的;

  3. jsx 中使用数组, 最常见的就是 map, 在 jsx 中遇到数组会自动展开, React.createElement() 在处理是第三个参数是数组还是元素。

JSX 注释

let ele = (
  <div>
    //<span>comment text</span>
    <span>some text</span>
  </div>
);

上述代码并不会报错, 但是却达不到注释的目的, 将会会在浏览器中显示 // 和两个 span, 原因 是React.createElement()

正确的姿势是用 {} 将注释部分包裹起来.

let ele = (
  <div>
    {/*<span>comment text</span>*/}
    <span>some text</span>
  </div>
);

注意行间注释不要使用 {}

let ele = (
  <div>
    <span
      clasName="demo"
      /*
       * 行间注释, 不能加{}, 否则会报语法错误, props里不能干巴巴的放个大括号进去
       ***/
    >
      some text
    </span>
  </div>
);

JSX 小结:

JSX 的特点:

  • 允许使用熟悉的语法定义 HTML 元素树
  • 更加语义化,允许自定义标签及组件
  • 更加直观,标签处理方式,更加可读
  • 抽象了 ReactElement 的创建过程
  • 关注点分离, 模块化,JSX 以干净且简洁的方式保证了组件中的标签与所有业务逻辑的互相分离
  • 原生的 js

组件的生命周期

一个 React 组件就是一个状态机, 随着该组件的 props 或者 state 发生改变, 它的 DOM 表现也会有相应的变化, React 为每个组件提供了生命周期钩子函数去相应不同的时刻, 创建, 存在, 销毁. (以 es5 写法为例说明, es6 略有不同)

实例化

一个实例初次被创建时的生命周期方法与其他各个后续实例被创建时所调用的方法略有不同. 首次创建一个组件类时下列方法被依次调用:

  • getDefaultProps
  • getInitialState
  • componentWillMount
  • render
  • componentDidMount

对于该组件的所有后续创建的实例, getDefaultProps 不再被调用

  • getInitialState
  • componentWillMount
  • render
  • componentDidMount

** getDefaultProps **

在 es5 的写法中这个方法只会被调用一次, 不管有多少个组件实例都是这样, 为未指定 props 的实例设置默认属性.

任何复杂的值都在实例中共享, 并没有发生拷贝和克隆

getInitialState

对于每个实例来讲该方法只调用一次, 这一点与 getDefaultProps 不一样, 在这里可以访问到 this.props

const Hello = React.createClass({
  getDefaultProps() {
    return {
      num: value,
    };
  },
  getInitialState() {
    return {
      num: this.props.num * 10,
    };
  },
});

componentWillMount

该方法在实例被渲染之前调用, 可以在 render 之前最后一次修改组件的 state

render

创建一个虚拟 DOM, 用来表示组件的输出, 对于一个组件来讲, render 是必需的一个方法, 一般满足以下几点:

  • 只能通过 this.propsthis.state 访问数据
  • 可以返回 nullfalse 或者任何的 React 组件
  • 只能出现一个顶级组件,不能返回一组元素
  • 保持纯净,意味着不能改变组件的状态或者修改 DOM 的输出

componentDidMount

render 成功调用并且真实的 DOM 已经被渲染后, 可以在 componentDidMount 内部使用 ReactDOM.findDOMNode(this) 访问到它.

存在期

这个周期内, 组件已经可以和用户进行交互了, 用户改变了组件或者整个应用的 state 会有新的 state 流入组件树

componentWillReceiveProps

用此函数可以作为 react 在 prop 传入之后, render() 渲染之前更新 state 的机会。老的 props 可以通过 this.props 获取到。在该函数中调用 this.setState() 将不会引起第二次 render

componentWillReceiveProps:function(nextProps){
    this.setState({
        isVisible: true
    });
}

shouldComponentUpdate

boolean shouldComponentUpdate(object nextProps, object nextState)

在接收到新的 props 或者 state,将要渲染之前调用。该方法在初始化渲染的时候不会调用,在使用 forceUpdate 方法的时候也不会。

如果确定新的 propsstate 不会导致组件更新,则此处应该 返回 false。

shouldComponentUpdate: function(nextProps, nextState) {
    return nextProps.someVlaue !== this.props.someVlaue;
}

如果 shouldComponentUpdate 返回 false,则 render() 将不会执行,直到下一次 state 改变。另外,componentWillUpdatecomponentDidUpdate 也不会被调用。

默认情况下,shouldComponentUpdate 总会返回 true,建议使用 React15.3 版本及以后版本添加的 PureComponent 作为父级继承, 必要时你可以覆盖 shouldComponentUpdate 方法,实现新老 propsstate 的比对逻辑。使用 shouldComponentUpdate 可以提升应用的性能。

componentWillUpdate

componentWillUpdate(object nextProps, object nextState)

在接收到新的 props 或者 state 之前立刻调用, 使用该方法做一些更新之前的准备工作。在初始化渲染的时候该方法不会被调用。 如果需要更改 state 的话可以在 componentWillReceiveProps 周期中进行操作 state

componentDidUpdate

componentDidUpdate(object prevProps, object prevState)

在组件的更新已经同步到 DOM 中之后立刻被调用。该方法不会在初始化渲染的时候调用。使用该方法可以在组件更新之后操作 DOM 元素。

销毁期

componentWillUnmount

componentWillUnmount()

在组件从 DOM 中移除的时候立刻被调用。在该方法中执行任何必要的清理,比如无效的定时器,或者清除在 componentDidMount 中创建的 DOM 元素。