React 高级指导 笔记

深入JSX

date:20170412
笔记原文
其实JSX是React.createElement(component, props, ...children)的语法糖。

JSX代码:

<MyButton color="blue" shadowSize={2}>
  Click Me
</MyButton>

将会被编译为

React.createElement(
  MyButton,
  {color: 'blue', shadowSize: 2},
  'Click Me'
)

如果没有包含子标签,你也可以用自闭标签:

<div className="sidebar" />

将被编译为

React.createElement(
  'div',
  {className: 'sidebar'},
  null
)

在线Bable编译器可以测试你的JSX代码将会被编译成的JS代码。

指定React 元素类型

JSX标签的第一部分决定了React元素的类型。类型首字母大写说明引用的是React组件,而不是html标签。在当前文件中,应该引入使用的组件。

React 必须要在上下文中

例如:

import React from 'react';
import CustomButton from './CustomButton';

function WarningButton() {
  // return React.createElement(CustomButton, {color: 'red'}, null);
  return <CustomButton color="red" />;
}

导入React和CustomButton都是必须的。

在JSX类型中,使用.表达式

在JSX中,也可以用点表达式来引用React组件。如果你在一个类型中声明了很多React组件,这会方便引用。

import React from 'react';

const MyComponents = {
  DatePicker: function DatePicker(props) {
    return <div>Imagine a {props.color} datepicker here.</div>;
  }
}

function BlueDatePicker() {
  return <MyComponents.DatePicker color="blue" />;
}

自定义组件名首字母必须大写

如果类型首字母是小写时,说明它是一个内建的组件就像divspan。类型首字母大写的时候,将会编译为React.createElement(Foo),并且对应于在JS中定义的组件。
我们建议将自定义组件的首字母大写。如果组件名是小写的时候,那必须在使用之前,赋值给大写变量。
这是一个错误的案例。

import React from 'react';

// Wrong! This is a component and should have been capitalized:
function hello(props) {
  // Correct! This use of <div> is legitimate because div is a valid HTML tag:
  return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
  // Wrong! React thinks <hello /> is an HTML tag because it's not capitalized:
  return <hello toWhat="World" />;
}

为了修改这个bug,我们要将小写组件名变为大写;

import React from 'react';

// Correct! This is a component and should be capitalized:
function Hello(props) {
  // Correct! This use of <div> is legitimate because div is a valid HTML tag:
  return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
  // Correct! React knows <Hello /> is a component because it's capitalized.
  return <Hello toWhat="World" />;
}

在运行的时候选择类型

要实现这样的功能,下面的代码是不可以的:

import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
  // Wrong! JSX type can't be an expression.
  return <components[props.storyType] story={props.story} />;
}

要想通过prop来决定渲染哪个组件,必须先要把它赋值给一个大写变量:

import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
  // Correct! JSX type can be a capitalized variable.
  const SpecificStory = components[props.storyType];
  return <SpecificStory story={props.story} />;
}

JSX中的Props

在JSX中指定props有几种方法

JavaScript 表达式

可以在JSX中插入任何的JS表达式,例如:

<MyComponent foo={1 + 2 + 3 + 4} />

这里MyComponent组件的foo属性九是10,因为表达式已经计算好了。
iffor语句不是表达式。所以不能直接使用。但是我们可以用变量来存储代码。

function NumberDescriber(props) {
  let description;
  if (props.number % 2 == 0) {
    description = <strong>even</strong>;
  } else {
    description = <i>odd</i>;
  }
  return <div>{props.number} is an {description} number</div>;
}

字符串字面量

我们可以直接将字符串赋值给props。以下两种方法是一样的:

<MyComponent message="hello world" />

<MyComponent message={'hello world'} />

如果直接传递字符串,HTML的标签要转义。

<MyComponent message="<3" />

<MyComponent message={'<3'} />

Props默认为 True

如果不在props中传值,默认是true。以下两个表达式是一致的:

<MyTextBox autocomplete />

<MyTextBox autocomplete={true} />

通常,我们不建议用这种方法,因为按照ES6对象简写中的语法,{foo}表示的是{foo: foo}而不是{foo:true}。这种逻辑之所以存在,是因为符合HTML的逻辑。

属性展开

如果你有一个props对象,并且想要将它传递给JSX,你可以使用...来展开对象。以下代码效果是一致的:

function App1() {
  return <Greeting firstName="Ben" lastName="Hector" />;
}

function App2() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  return <Greeting {...props} />;
}

这个方法,在构建自定义的组件的时候很有用。但是也会使代码变乱,因为这样做也会把不需要的变量也传递进去。所以这个特性也得谨慎的使用。

JSX中的子类

在JSX表达式中,也会有开放的标签和封闭的标签。但是在封闭标签之中的内容会通过props.children传递给组件。以下是传递子类的几种方法。

字符串字面量

直接将字符串用标签包裹起来,那么props.children就是该字符串。这个写法很常见。

<MyComponent>Hello world!</MyComponent>

MyComponent组件里的props.children就是字符串Hello world!。HTML需要转义。

<div>This is valid HTML & JSX at the same time.</div>

JSX会将行首和行尾的空白字符删掉,也会把空白行删除。和标签毗邻的空行会被删除;在字符串之间的空行也会被删除。以下的代码渲染结果都是一样的。

<div>Hello World</div>

<div>
  Hello World
</div>

<div>
  Hello
  World
</div>

<div>

  Hello World
</div>

JSX后代

可以直接将JSX元素作为子代传入。在嵌套渲染的时候很有用:

<MyContainer>
  <MyFirstComponent />
  <MySecondComponent />
</MyContainer>

两种类型混合也是可以的。

<div>
  Here is a list:
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
  </ul>
</div>

一个React组件不能返回多个React元素,但是一个简单的JSX表达式可以拥有很多个后代。所以如果想要渲染多个视图,可以把这些视图用<div>包裹起来。

javascript表达式

可以嵌入任何JavaScript表达式,但是要用{}包裹起来。以下两种写法是一致的:

<MyComponent>foo</MyComponent>

<MyComponent>{'foo'}</MyComponent>

这个写法对于渲染一个任意长度的列表视图是很有用的。例如:

function Item(props) {
  return <li>{props.message}</li>;
}

function TodoList() {
  const todos = ['finish doc', 'submit pr', 'nag dan to review'];
  return (
    <ul>
      {todos.map((message) => <Item key={message} message={message} />)}
    </ul>
  );
}

js表达式可以和其他类型的混合起来。通常在用变量替代字符串。

function Hello(props) {
  return <div>Hello {props.addressee}!</div>;
}

传递函数作为后代

通常JSX中的js表达式会当作字符串,React元素或者一个数组。然而,props.children就像其他的属性一样,可以传递任何数据。例如,我们可以在自定义的组件中,传递一个回调函数:

// Calls the children callback numTimes to produce a repeated component
function Repeat(props) {
  let items = [];
  for (let i = 0; i < props.numTimes; i++) {
    items.push(props.children(i));
  }
  return <div>{items}</div>;
}

function ListOfTenThings() {
  return (
    <Repeat numTimes={10}>
      {(index) => <div key={index}>This is item {index} in the list</div>}
    </Repeat>
  );
}

这种用法是不太常见。

Boolean Null 和Undifined会被忽略

false,null,undefinedtrue都是合理的后代。但是通常他们不会被渲染。以下代码渲染的结果都是一样的:

<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{undefined}</div>

<div>{true}</div>

在条件渲染的时候是很有用的。例如:

<div>
  {showHeader && <Header />}
  <Content />
</div>

只有在showHeader为true的时候,才会渲染<Header>
需要注意的是,0不是false。例如:

<div>
  {props.messages.length &&
    <MessageList messages={props.messages} />
  }
</div>

要修改这个bug,可以这么改:

<div>
  {props.messages.length > 0 &&
    <MessageList messages={props.messages} />
  }
</div>

相反的,如果想要渲染false,true,null或者undefined的时候,需要先转换为String

<div>
  My JavaScript variable is {String(myVariable)}.
</div>

DOM和Refs

date:20170413
原文链接
按照典型的React数据流,[props][react-props]是父控件影响子控件的唯一方式。如果要修改子控件,那就必须用新的prop重新渲染。但有时候我们需要另外的方法才能满足需求。

什么时候使用Refs

refs的使用场景:

  • 焦点管理,文本选择或者媒体回放
  • 触发动画
  • 集成第三方库
    我们不应该滥用refs。
    例如,在控制对话框的显示上,我们不应该暴露open()或者close()接口,用isOpen的props属性就好了。

在DOM元素中添加Ref

React 通过ref属性来传递一个回调函数,这个回调函数在组件的加载和卸载的时候马上执行。当在HTML元素中使用ref的时候,当前的HTML元素回作为参数传递给它。例如:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.focus = this.focus.bind(this);
  }

  focus() {
    // Explicitly focus the text input using the raw DOM API
    this.textInput.focus();
  }

  render() {
    // Use the `ref` callback to store a reference to the text input DOM
    // element in an instance field (for example, this.textInput).
    return (
      <div>
        <input
          type="text"
          ref={(input) => { this.textInput = input; }} />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focus}
        />
      </div>
    );
  }
}

以上代码中,React会在转载的时候马上回调ref回调函数,对textInput进行初始化,然后在卸载的时候将textInput设置为null
使用ref回调是操作DOM的一种固定模式。以上的有关refs的代码也可以简写为ref={input => this.textInput = input}

在类组件中定义Ref

ref属性用在通过类定义的组件里的时候,ref获取到的是加载的类组件。例如:

class AutoFocusTextInput extends React.Component {
  componentDidMount() {
    this.textInput.focus();
  }

  render() {
    return (
      <CustomTextInput
        ref={(input) => { this.textInput = input; }} />
    );
  }
}

这里获取到的就是CustomTextInput组件。
需要注意的是,这只有在CustomTextInput声明为类的时候才有效果。

Refs和函数组件

在函数定义的组件中是不可以使用ref的,因为它不具有实例。
这个例子就不会有效:

function MyFunctionalComponent() {
  return <input />;
}

class Parent extends React.Component {
  render() {
    // 这里就会**出错**
    return (
      <MyFunctionalComponent
        ref={(input) => { this.textInput = input; }} />
    );
  }
}

你必须将它转换为类函数,就像需要生命周期和state一样。这些都只能出现在类组件的特性。
但是你可以在函数组件中的DOM元素或者类组件中使用

function CustomTextInput(props) {
  // textInput must be declared here so the ref callback can refer to it
  let textInput = null;

  function handleClick() {
    textInput.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={(input) => { textInput = input; }} />
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );  
}

不能滥用Refs

如果需要在app中使用ref,那就有必要花点时间想想state应该在组件关系中的那个层级。通常是在较高的层级(父级)。参见提升state

遗留的API:字符串Refs

以前的版本中可以通过this.refs.textInput来引用,但是这会引起问题。这个api将来会在某个版本移除,所以应该用回调的模式使用ref

要注意的陷阱

如果ref通过单行函数定义的时候,在更新的时候会执行两次。第一次是null,第二次正常。这是因为react更新的时候会重新生成一个组件实例,所以之前的实例销毁,设置为null,当创建了新的实例之后,又传递进来。你可以通过定义方法(a bound method,非单行函数)来避免这个问题,但是通常情况下并不影响。


非控制组件

date:20170414
笔记原文
通常情况下,我们推荐使用控制组件来实现表单。在控制组件中,React组件来处理表单数据,但是在非控制组件中,DOM自己来处理组件。
在实现非控制组件的时候,我们不用事件监听来获取state的变化,我们使用ref从DOM中获取数据。举个例子,通过非控制组件来获取姓名:

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.input.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" ref={(input) => this.input = input} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

因为非控制组件直接保存的是DOM元素,所以有时候会很方便的将React和非React组件结合起来。
如果你对什么时候使用那种类型的组件还不太清楚的时候,可以参看控制vs非控制组件文章。

默认值

在React的生命周期里,表单元素中的value值会覆盖DOM的值。在非控制组件中,常常需要指定初始值,这种情况下,可以直接指定defaultValue属性。

render() {
  return (
    <form onSubmit={this.handleSubmit}>
      <label>
        Name:
        <input
          defaultValue="Bob"
          type="text"
          ref={(input) => this.input = input} />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}

另外,<input type="checkbox"><input type="radio">支持defaultChecked属性,<select><textarea>支持defaultValue属性。


性能优化

date:20170418

笔记原文

React使用了一些技术技巧来减少DOM操作的开销。对于大多数应用来说,不需要优化就已经能够快速的呈现用户界面。尽管如此,这里也有几种方法来加速React应用。

使用发布版本

如果你在测试app性能,那么确认使用最小化过的发布版本,我一开始选用了Create-React-App的构建工具,所以这里我只记录这个工具的使用。原文中还有其他工具的使用方法。

  • 在Create React App 中,我们使用npm run build命令。
    因为版本有很多调试信息会使得app变慢,所以很有必要使用发布版本来做新能分析和产品发布。

组件在chrome中的表现

在开发版本模式,我们使用性能工具能够以图表的方式查看加载组件,更新组件和卸载组件的性能。

  1. 在url后面添加?react_perf
  2. 打开Chrome DevTool TimeLine选项卡,并点击Record
  3. 开始操作app,事件不要超过20s,不然会导致Chrome挂起。
  4. 停止记录。
  5. 追踪到的记录会在User Timing选项卡中显示。
    需要注意到的是这些数值都是相对的,在发布版本中可能会渲染得更快。但是你可以发现一些错误,比如渲染了无关的模块。还可以发现模块更新的频繁程度。
    目前这个功能只能在Chrome,Edge和IE傻姑娘支持,不过可以使用User Timing API来支持更多的浏览器。

避免视图刷新

React使用了自己的一套渲染UI的方法。React不需要创建DOM结点,也不需要操作它们,因为操作dom比操作js对象慢。这里用的方法就是虚拟DOM。
当组件的props或者state变化的时候,react会对比返回的元素和之前的元素,如果不同,就会更新视图。
有时候,你也可以将这个过程加速:复写生命周期方法shouldComponentUpdate,这个回调会在渲染过程开始之前回调。默认的实现如下,始终返回true,来刷新视图:

shouldComponentUpdate(nextProps, nextState) {
  return true;
}

如果有些情况下,你确定不需要刷新的时候,那就直接返回false,来避免刷新。

使用 shouldComponentUpdate 生命周期方法

原文在这里用一个例子来说明界面更新的逻辑。这里就简单总结下:如果shouldComponentUpdate返回true,就去对比状态是否改变,如果改变了,就刷新,没有改变就不刷新。shouldComponentUpdate返回false,那就说没有必要刷新了,也不会有比较状态这个步骤了。

举例

如果你的组件,只有在props.colorstate.count变化的情况下才需要刷新界面,你必须要在shouldComponentUpdate中检查:

class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

这种模式很简单,反正就检查下关心的变量。React也提供了实现这个模式的辅助类,React.PureComponent。以下的代码效果与之前一样。

class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

大多数情况下,就使用React.PureComponent就可以了,不用自己实现shouldComponentUpdate
这里原文提到了一个浅对比(shallow comparsion),对比文章后面的内容,我理解为:对比变量的引用,而不是数据。例如数组,并不对比数组中的每个值,而是这个数组引用。
但是,对于复杂的数据结构就会有问题了。例如有个ListOfWords的控件用于渲染逗号隔开的一组词汇,并放置在WordAdder的控件中。WordAdder的功能就是通过点击按钮,给ListOfWords添加单词。以下的代码存在一个bug:

class ListOfWords extends React.PureComponent {
  render() {
    return <div>{this.props.words.join(',')}</div>;
  }
}

class WordAdder extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      words: ['marklar']
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    // This section is bad style and causes a bug
    const words = this.state.words;
    words.push('marklar');
    this.setState({words: words});
  }

  render() {
    return (
      <div>
        <button onClick={this.handleClick} />
        <ListOfWords words={this.state.words} />
      </div>
    );
  }
}

这个问题是由于PrueComponent只是简单的比较了this.props.words,由于数组的引用不变,得出的比较是相等的,尽管数组的内容已经变了。

不要直接改变数据

要避免这个问题,最简单的方法就是,避免直接改变props和state的值。例如,上面的handleClick方法可以重写成这样:

handleClick() {
  this.setState(prevState => ({
    words: prevState.words.concat(['marklar'])
  }));
}

对于操作数组,ES6有个展开语法,使用起来会比较简单。在Create React App 脚手架里,这个功能是默认可用的,其他脚手架就不知道了。

handleClick() {
  this.setState(prevState => ({
    words: [...prevState.words, 'marklar'],
  }));
};

对于对象,我们也可以使用类似的方法来避免突变。例如,以下的方法:

function updateColorMap(colormap) {
  colormap.right = 'blue';
}

需要修改为:

function updateColorMap(colormap) {
  return Object.assign({}, colormap, {right: 'blue'});
}

现在updateColorMap直接返回一个新的对象,而不是在原来的对象上修改。
有个JS提议,希望添加对象属性展开语法,那么代码就可以修改为下面的样子了:

function updateColorMap(colormap) {
  return {...colormap, right: 'blue'};
}

现在是不可以直接这么写的。

使用不可变的数据结构

Immutable.js是另外一种解决问题的方法。它通过结构分享,提供了永久的,不可变的集合。

  • 不可变:一旦集合创建了,就不会再改变了
  • 永久:新的集合可以从以前的集合创建出来。但是原来的集合也是可以使用的。
  • 结构共享:新的集合会使用原来的数据结构,以避免拷贝数据影响细性能。
    这种不可变的特性使得追踪数据变化变得很简单。因为数据的变化总是会导致对象的变化,所以我们就只需要对比引用有没有变化。例如:
const x = { foo: 'bar' };
const y = x;
y.foo = 'baz';
x === y; // true

这里,尽管y已经改变了,但是和x的引用一样,所以,对比表达式返回的还是true。用immutablejs重写的代码如下:

const SomeRecord = Immutable.Record({ foo: null });
const x = new SomeRecord({ foo: 'bar' });
const y = x.set('foo', 'baz');
x === y; // false

另外两个类似的库是seamless-immutableimmutability-helper.
不可变这一机理可以让我们很方便的跟踪数据变化,同时也提升性能有很大的帮助。


不使用ES6时的React

date:20170420

笔记原文

很多时候,我们会通过类的形式定义组件:

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

如果你还没有开始使用ES6,那么你可以使用Create-react-class模块:

var createReactClass = require('create-react-class');
var Greeting = createReactClass({
  render: function() {
    return <h1>Hello, {this.props.name}</h1>;
  }
});

ES6的API和createReactClass()是一样的效果。

定义默认Props

在类组件中,我们可以将默认的属性像组件的属性一样定义:

class Greeting extends React.Component {
  // ...
}

Greeting.defaultProps = {
  name: 'Mary'
};

如果通过creatReactClass()方法,你需要定义getDefaultProps()函数:

var Greeting = createReactClass({
  getDefaultProps: function() {
    return {
      name: 'Mary'
    };
  },

  // ...

});

设置初始状态

在ES6中,你可以在构造函数中对this.state赋值,来定义初始状态。

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: props.initialCount};
  }
  // ...
}

如果通过creatReactClass()方法,你需要定义getInitialState()函数:

var Counter = createReactClass({
  getInitialState: function() {
    return {count: this.props.initialCount};
  },
  // ...
});

自动绑定

在ES6定义的组件中,方法与ES6的类具有相同的语义。这说明,我们不会给实例自动绑定this,我们需要在构造函数中手动的调用.bind(this)来绑定。

class SayHello extends React.Component {
  constructor(props) {
    super(props);
    this.state = {message: 'Hello!'};
    // This line is important!
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    alert(this.state.message);
  }

  render() {
    // Because `this.handleClick` is bound, we can use it as an event handler.
    return (
      <button onClick={this.handleClick}>
        Say hello
      </button>
    );
  }
}

createReactClass()中,我们就不需要手动绑定了,那都是自动完成的:

var SayHello = createReactClass({
  getInitialState: function() {
    return {message: 'Hello!'};
  },

  handleClick: function() {
    alert(this.state.message);
  },

  render: function() {
    return (
      <button onClick={this.handleClick}>
        Say hello
      </button>
    );
  }
});

这意味着ES6类会在定义事件处理函数上都写些代码,但是这样做的好处是能够提升大型应用的性能。如果这烦人的代码对你没有吸引力,你可以在Babel中开启实验的类属性语法提议

class SayHello extends React.Component {
  constructor(props) {
    super(props);
    this.state = {message: 'Hello!'};
  }
  // WARNING: this syntax is experimental!
  // Using an arrow here binds the method:
  handleClick = () => {
    alert(this.state.message);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Say hello
      </button>
    );
  }
}

需要注意的是,这个功能还在实验阶段,语法可能会改变,或者提议会被放弃。
如果你想要安全些,你有几个注意的地方:

  • 在构造函数中绑定
  • 使用箭头函数,onClick=>{(e) => this.handleClick(e)}.
  • 使用createReactClass

Mixins

注意:
ES6并不支持Mixins,所以在React的ES6类中,不支持任何的Mixins.
我们发现在使用Mixins之后会有很多问题,所以我们不推荐使用。
这个章节只是为了提及这一点。

有时候,个别组件需要公用一些共同的函数。这涉及到了交叉剪切?(cross-cutting)的概念createReactClass了一使用遗留的mixins机制来实现。
一个很常见的用法是,组件需要通过interval来更新自己。使用setInterval()很简单,但是要在不需要定时的时候取消定时,来减少内存的消耗。React提供了生命周期方法让你知道组件的创建和销毁。举个例子:

var SetIntervalMixin = {
  componentWillMount: function() {
    this.intervals = [];
  },
  setInterval: function() {
    this.intervals.push(setInterval.apply(null, arguments));
  },
  componentWillUnmount: function() {
    this.intervals.forEach(clearInterval);
  }
};

var createReactClass = require('create-react-class');

var TickTock = createReactClass({
  mixins: [SetIntervalMixin], // Use the mixin
  getInitialState: function() {
    return {seconds: 0};
  },
  componentDidMount: function() {
    this.setInterval(this.tick, 1000); // Call a method on the mixin
  },
  tick: function() {
    this.setState({seconds: this.state.seconds + 1});
  },
  render: function() {
    return (
      <p>
        React has been running for {this.state.seconds} seconds.
      </p>
    );
  }
});

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

如果在组件中的几个生命周期方法中定义了多个mixins方法,所有的生命周期方法都会按顺序被执行。
好了,这个Mixins是被废弃了,也不需要做过多的了解。


不使用JSX时的React

date:20170421
原文链接

在使用React的时候,JSX也不是必须。当你不想配置jsx的编译环境的时候,不使用JSX会尤其方便些。JSX是React.createElement(component,props,...children)的语法糖,所以不使用JSX的时候,可以直接使用这个底层的方法。
例如,用JSX的代码如下:

class Hello extends React.Component {
  render() {
    return <div>Hello {this.props.toWhat}</div>;
  }
}

ReactDOM.render(
  <Hello toWhat="World" />,
  document.getElementById('root')
);

编译为非JSX版本的代码如下:

class Hello extends React.Component {
  render() {
    return React.createElement('div', null, `Hello ${this.props.toWhat}`);
  }
}

ReactDOM.render(
  React.createElement(Hello, {toWhat: 'World'}, null),
  document.getElementById('root')
);

如果你对JSX如何编译为JavaScript很好奇,那么你可以看看在线Babel编译器.组件可以是字符串,也可以是React.Component的子类,也是可以一个不包含state的函数组件。
如果你觉得React.createElement输入太多,一个简单的方法是将它赋值给简短的变量:

const e = React.createElement;

ReactDOM.render(
  e('div', null, 'Hello World'),
  document.getElementById('root')
);

看吧,这也跟使用JSX一样方便了,对吧。


视图刷新

date:20170421
原文链接
React提供了一组API使得我们可以不需要完全了解每次界面更新。这使得编码简单化了,但是我们不知道里边的原理。这篇文章就来介绍下为什么我们会选择这么奇特的逻辑。

动机

使用React的时候,在某个简单的点上,调用render()会生成一个树状的React元素。当props或者state更新的时候,render()返回新的React元素。这时候React就需要快速有效的找到需要跟新的元素。
对于这个逻辑问题,有许多普通的解决方法:找到变换UI的最小的操作次数。但是这个逻辑的复杂度是O(n3),n是渲染树中的元素。
如果我们在React中使用了1000个元素,那么将要进行10亿次比较。开销太大。React基于两个假设,使用的是O(n)的逻辑:
1. 两个不同的元素渲染不同的界面。
2. 开发者通过key属性,提示是否要刷新。
在实际运用中,这些假设几乎在所有的情况下都可行。

对比差异的逻辑

获取到两个树之后,React首先会比较两个根元素。之后的对比会根据根元素的不同有些差异。

不同类型的元素

如果根目录的元素不一样的时候,React会重新渲染整个树。从<a>变为<img><Article>变为<Comment><Button>变为<div>。所有的这些都会导致重新构建。
当出去了原来的渲染树的时候,所有的元素会被销毁,回调componentWillUnmount()。新的DOM会插入。新的组件回调componentWIllMount(),然后componentDidMount()。所有以前的元素,将会消失。
所有根元素下的组件会卸载,状态会销毁。例如:

<div>
  <Counter />
</div>

<span>
  <Counter />
</span>

旧的Counter会销毁,新的Counter会挂载上去。

相同类型的DOM元素

对比相同类型的元素的时候,React会查看元素的所有属性,保留相同的,更新变化的。例如:

<div className="before" title="stuff" />

<div className="after" title="stuff" />

对比上面的两个组件,React只会更新组件的类名。
当更新样式的时候,React也只会更新变化的部分:

<div style={{color: 'red', fontWeight: 'bold'}} />

<div style={{color: 'green', fontWeight: 'bold'}} />

这里,React只会更新color属性,不会更新fontWeight属性。
在父节点更改之后,会遍历子节点。

相同类型的组件元素

当组件更新的时候,并不重新创建新的元素。所以组件的状态都会保持。React直接更新组件的Props,而不会去比较它。组件回调componentWillReceiveProps()componentWillUpdate()方法。
接着,回调render()函数,然后再对比组件中的差异。

子组件对比逻辑

默认情况下,React会同时遍历新旧组件的子组件,当对比出差异的时候,直接更改。
例如,我们在列表底部添加子元素的时候:

<ul>
  <li>first</li>
  <li>second</li>
</ul>

<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>

我们会对比两个<li>first</li>,对比<li>second</li>,然后在插入<li>third</li>。如果你直接在第一项中添加了一项,那么性能就会很糟糕:

<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

React并不会意识到其中的两项不必更新,所以这个列表的更新就会比较低效。

keys

React通过key参数,来解决上述列表更新的问题:通过比较key来决定是否更新列表。

<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

这样,React就可以发现,只有key为2014的元素是新的,另外两个只要移动下位置就可以了。
实际上,给key赋值也是很简单的。很多时候数据本身带有唯一的id。

<li key={item.id}>{item.name}</li>

如果没有这个key,你可以添加一个属性,或是进行hash运算,计算出一个唯一的值。键值在列表中必须是唯一的,但是在全局中可以不同。你也可以用数据项的索引来充当键值。如果不重新排序,这个做法没有什么问题,但是要重新排序就会有问题了。

总结(tradeoffs)

熟悉React的更新逻辑是很重要的。在触发每个动作之后,React会重新渲染整个app,尽管有些时候UI并没有变化。
在当前的实现中,你可以解释子树在同级间的移动,但是不知道移动到哪里了。这套算法会重新渲染整个子树。
由于React的这个实现逻辑是发散式的,如果这个实现逻辑背后的假设并不成立的话,性能就会很差。

  1. 这个算法并不会比较不同类型组件的子组件。如果你的两个子组件很相似,并且经常切换,那么需要将它们合二为一。在实际应用中,我们也没有发现什么问题。
  2. key必须要稳定的,可以预测的以及唯一的。不稳定的key(通过Math.random()生成的key)将会导致很多组件实例和DOM结点没有必要的重建,影响性能,同时状态也不会保留下来。

上下文

date:20170422
笔记原文

注意:由于在React v15.5中React.PropTypes已经废弃了,所以我们推荐使用prop-types库来定义contextTypes.

React可以很容易地跟踪数据流。当你查看一个组件的时候,你可以查看传递了哪个prop,这可以很容易找到问题。
有时候,你不需要通过传递props来传递数据。你可以通过上下文传递数据。

为什么不使用上下文

很多app并不需要使用上下文。

  • 如果你想让app更加稳定,就不要使用上下文。这个一个试验功能,可能在以后的版本中废除。
  • 如果你不熟悉状态管理库Redux或者Mobx,就不要使用context。推荐使用Redux,因为很多组件都基于这些三方库。
  • 如果你不是经验丰富的React开发者,就不要用context。
  • 如果你一定要使用context,那么将代码分离,限定在小范围内。这有利于升级API。

如何使用Context

假设你有如下的结构:

class Button extends React.Component {
  render() {
    return (
      <button style={{background: this.props.color}}>
        {this.props.children}
      </button>
    );
  }
}

class Message extends React.Component {
  render() {
    return (
      <div>
        {this.props.text} <Button color={this.props.color}>Delete</Button>
      </div>
    );
  }
}

class MessageList extends React.Component {
  render() {
    const color = "purple";
    const children = this.props.messages.map((message) =>
      <Message text={message.text} color={color} />
    );
    return <div>{children}</div>;
  }
}

在这个例子中,我们通过props传递color参数。如果使用context,那么代码如下:

const PropTypes = require('prop-types');

class Button extends React.Component {
  render() {
    return (
      <button style={{background: this.context.color}}>
        {this.props.children}
      </button>
    );
  }
}

Button.contextTypes = {
  color: PropTypes.string
};

class Message extends React.Component {
  render() {
    return (
      <div>
        {this.props.text} <Button>Delete</Button>
      </div>
    );
  }
}

class MessageList extends React.Component {
  getChildContext() {
    return {color: "purple"};
  }

  render() {
    const children = this.props.messages.map((message) =>
      <Message text={message.text} />
    );
    return <div>{children}</div>;
  }
}

MessageList.childContextTypes = {
  color: PropTypes.string
};

通过在MessageList中添加childContextTypesgetChildContext。React自动传递了参数,任何子组件可以通过contextTypes获取参数。
如果contextTypes没有定义,那么context就会为空。

父组件与子组件间通讯

Context可以实现父子组件间的通讯。例如,React Router V4就是按照这个方法来的。

import { BrowserRouter as Router, Route, Link } from 'react-router-dom';

const BasicExample = () => (
  <Router>
    <div>
      <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/about">About</Link></li>
        <li><Link to="/topics">Topics</Link></li>
      </ul>

      <hr />

      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
      <Route path="/topics" component={Topics} />
    </div>
  </Router>
);

通过Router组件向下传递了一些信息,每个LinkRoute都可以向Router传递信息。
当你这样的方式实现逻辑的时候,考虑下是否有个更加清楚的替代方法。例如你可以通过props传递React参数。

在生命周期函数中引用Context

如果在组件中定义了contextTypes,以下的生命周期方法,将会获取到context对象。

  • constructor(props,context)
  • componentWillReceiveProps(nextProps,nextContext)
  • shouldComponentUpdate(nextProps,nextState,nextContext)
  • componentWillUpdate(nextProps,nextState,nextContext)
  • componentDidUpdate(preProps,prevState,prevContext)

在不包含状态的函数组件中引用Context

如果定义了函数组件的contextTypes参数,也可以使用context。例如下面的Button组件:

const PropTypes = require('prop-types');

const Button = ({children}, context) =>
  <button style={{background: context.color}}>
    {children}
  </button>;

Button.contextTypes = {color: PropTypes.string};

更新Context

千万别更新Context。React提供了API来更新上下文。
当state或者props更新的时候,getChildContext函数会被回调,来更新context中的数据。state的更新使用this.setState方法。这会触发生成新的context,自组件也会获取到新的数据。

const PropTypes = require('prop-types');

class MediaQuery extends React.Component {
  constructor(props) {
    super(props);
    this.state = {type:'desktop'};
  }

  getChildContext() {
    return {type: this.state.type};
  }

  componentDidMount() {
    const checkMediaQuery = () => {
      const type = window.matchMedia("(min-width: 1025px)").matches ? 'desktop' : 'mobile';
      if (type !== this.state.type) {
        this.setState({type});
      }
    };

    window.addEventListener('resize', checkMediaQuery);
    checkMediaQuery();
  }

  render() {
    return this.props.children;
  }
}

MediaQuery.childContextTypes = {
  type: PropTypes.string
};

这有个问题是,如果context跟新的时候,子组件如果在shouldComponentUpdate中返回了false就不会自动跟新了。这就会使得自组件使用context失去控制。所以没有基础的方法来更新context这篇博客详细阐述了这个问题的缘由以及如何去绕过这个问题。


web组件

date: 20170422

笔记原文

React和web组件用于解决不同的问题。web组件可以提供高度封装好的,可以重用的组件。然而React提供了声明好的库。它们相辅相成。开发者可以自由的在web组件中使用React,或者在React中使用web组件。
很多人会使用React,但是不使用web组件。但是你可能使用了用web组件实现的第三方库。

在React中使用web组件

class HelloMessage extends React.Component {
  render() {
    return <div>Hello <x-search>{this.props.name}</x-search>!</div>;
  }
}

注意:
web组件可能会提供一组接口。例如videoweb组件需要暴露play()pause()方法。如果要使用这些API,那么你要做下封装,然后可以直接控制DOM结点。如果你使用第三方的web组件,最好的方法是用React来封装web组件。
web组件触发的事件可能不会被React捕捉到。你需要自己实现事件监听。

有个容易出错的地方就是在web组件中使用class而不是className.

function BrickFlipbox() {
  return (
    <brick-flipbox class="demo">
      <div>front</div>
      <div>back</div>
    </brick-flipbox>
  );
}

在web组件中使用React

const proto = Object.create(HTMLElement.prototype, {
  attachedCallback: {
    value: function() {
      const mountPoint = document.createElement('span');
      this.createShadowRoot().appendChild(mountPoint);

      const name = this.getAttribute('name');
      const url = 'https://www.google.com/search?q=' + encodeURIComponent(name);
      ReactDOM.render(<a href={url}>{name}</a>, mountPoint);
    }
  }
});
document.registerElement('x-search', {prototype: proto});

高阶组件

date:20170426

笔记原文

高阶组件(HOC)是React重用组件的高级技术。HOC不是React API的一部分,而是React封装的一种模式。
其实,HOC是一个能够将一个组件变为另一个组件的函数。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

一个组件,将属性展示为UI;而一个高阶组件,将一个组件变为另一个组件。HOC在三方库中非常常见。例如Redux的connect和Relay的createContainer.
在这个文档中,我们将解释为什么HOC会很有用处,并且如何去实现自己的HOC。

HOC的横切关注点

注意:
我们之前会推荐使用mixins来处理横切关注点。我们后来意识到mixins会导致很多bug。详情参见这篇博客,阐述废弃mixins的原因以及如何更改原有代码。

组件是React里的最初单元。但是有时候你会发现有些模式使用传统的组件并不能满足条件。
例如,你有一个CommentList组件,监听外部的数据来渲染列表。

class CommentList extends React.Component {
  constructor() {
    super();
    this.handleChange = this.handleChange.bind(this);
    this.state = {
      // "DataSource" is some global data source
      comments: DataSource.getComments()
    };
  }

  componentDidMount() {
    // Subscribe to changes
    DataSource.addChangeListener(this.handleChange);
  }

  componentWillUnmount() {
    // Clean up listener
    DataSource.removeChangeListener(this.handleChange);
  }

  handleChange() {
    // Update component state whenever the data source changes
    this.setState({
      comments: DataSource.getComments()
    });
  }

  render() {
    return (
      <div>
        {this.state.comments.map((comment) => (
          <Comment comment={comment} key={comment.id} />
        ))}
      </div>
    );
  }
}

后来,你又需要监听博客的提交数据,使用的是相同的模式:

class BlogPost extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {
      blogPost: DataSource.getBlogPost(props.id)
    };
  }

  componentDidMount() {
    DataSource.addChangeListener(this.handleChange);
  }

  componentWillUnmount() {
    DataSource.removeChangeListener(this.handleChange);
  }

  handleChange() {
    this.setState({
      blogPost: DataSource.getBlogPost(this.props.id)
    });
  }

  render() {
    return <TextBlock text={this.state.blogPost} />;
  }
}

CommentListBlogPost是不同的组件,它们调用各自的方法,监听各自的不同的数据,渲染不同的输出。但是它们的模式是一样的:

  • 在加载组件的时候,对数据源添加监听函数。
  • 在监听函数中,当数据变化的时候调用setState
  • 在卸载的时候,移除监听。
    可以想象,在大型应用中,这种监听数据源,设置更新数据的模式经常会用到。所以我们需要把这部分功能抽象出来,并且在很多个组件中实现公用。这就是高阶组件做的事情。
    我们现在先来实现一个函数,用来生成类似CommentList或者BlogPost的组件。这个函数有两个参数,一个参数是需要监听数据源的组件,另一个参数是数据源。函数名称定义为withSubscription:
const CommentListWithSubscription = withSubscription(
  CommentList,
  (DataSource) => DataSource.getComments()
);

const BlogPostWithSubscription = withSubscription(
  BlogPost,
  (DataSource, props) => DataSource.getBlogPost(props.id)
});

第一个参数就是被包裹的组件,第二个参数就是提过我们感兴趣的数据的函数。
CommentListWithSubscriptionBlogPostWithSubscription渲染的时候,CommentListBlogPost将会被传入一个data属性:

// This function takes a component...
function withSubscription(WrappedComponent, selectData) {
  // ...and returns another component...
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.handleChange = this.handleChange.bind(this);
      this.state = {
        data: selectData(DataSource, props)
      };
    }

    componentDidMount() {
      // ... that takes care of the subscription...
      DataSource.addChangeListener(this.handleChange);
    }

    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange);
    }

    handleChange() {
      this.setState({
        data: selectData(DataSource, this.props)
      });
    }

    render() {
      // ... and renders the wrapped component with the fresh data!
      // Notice that we pass through any additional props
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

这里我们看到,HOC并不会改变输入的组件,也不复制组件。而是在容器中将原来的组件包裹起来。HOC是一个不影响任何一方的“纯净”组件。它获取到关于组件的所有参数,并赋值给新的变量data,用来渲染界面。HOC不关心数据的来源,也不关心数据的用法。
因为withSubscription是一个很平常的函数,你可以添加任意多的参数,来实现你想要的功能。
withSubscription和被包裹的组件之间是基于props的,所以在需要的时候替换HOC是很简单的。

不要改变原来的组件,选择包裹

不要在HOC中改变传递进来的组件:

function logProps(InputComponent) {
  InputComponent.prototype.componentWillReceiveProps(nextProps) {
    console.log('Current props: ', this.props);
    console.log('Next props: ', nextProps);
  }
  // The fact that we're returning the original input is a hint that it has
  // been mutated.
  return InputComponent;
}

// EnhancedComponent will log whenever props are received
const EnhancedComponent = logProps(InputComponent);

这样做会导致一些问题。首先就是输入组件脱离了这个强化组件就不能重用了。更重要的是,如果你传入另一个组件,也会改变componentWillReceiveProps函数,之前HOC的功能就被复写了。而且这个HOC也不支持函数组件,因为函数组件并没有生命周期。
这样的HOC并不是一个好的实现,使用者必须知道HOC是怎么实现的,才能避免与其他组件产生冲突。
所以,我们不能改变原来组件,而是重新生成一个组件,将输入组件包裹起来:

function logProps(WrappedComponent) {
  return class extends React.Component {
    componentWillReceiveProps(nextProps) {
      console.log('Current props: ', this.props);
      console.log('Next props: ', nextProps);
    }
    render() {
      // Wraps the input component in a container, without mutating it. Good!
      return <WrappedComponent {...this.props} />;
    }
  }
}

这个HOC和之前的直接修改的实现的功能是一样的,但是这个可以避免冲突,并且可以支持函数组件。由于它是纯组件,所以它和其他的HOC是兼容的。
你会注意到HOC和容器组件很相似。容器组件是为了分离不同级别之间的功能的一种策略。容器管理订阅和状态,传递props给组件。容器组件是HOC的一部分。HOC可以理解为参数化的容器组件。

公约:给包裹的组件只传递相关的props

HOC只是添加功能,不能对原有的组件做很大的变化。我们期望输入和输出只有温和的变化。HOC传递的参数,只是需要的参数,不相关的参数就不要传递进去:

render() {
  // Filter out extra props that are specific to this HOC and shouldn't be
  // passed through
  const { extraProp, ...passThroughProps } = this.props;

  // Inject props into the wrapped component. These are usually state values or
  // instance methods.
  const injectedProp = someStateOrInstanceMethod;

  // Pass props to wrapped component
  return (
    <WrappedComponent
      injectedProp={injectedProp}
      {...passThroughProps}
    />
  );
}

这个约定有利于HOC更加灵活和更容易实现重用。

公约:兼容性最大化

并不是所有的HOC都是一样的形式,有的只有一个参数,需要被包裹的组件:

const NavbarWithRouter = withRouter(Navbar);

通常,HOC需要额外的参数。举一个Relay的例子,为了说明数据的依赖,需要传递一个配置对象:

const CommentWithRelay = Relay.createContainer(Comment, config);

HOC常见的形式如下:

// React Redux's `connect`
const ConnectedComment = connect(commentSelector, commentActions)(Comment);

这个形式有点看不清楚,换个写法,你就知道了:

// connect is a function that returns another function
const enhance = connect(commentListSelector, commentListActions);
// The returned function is an HOC, which returns a component that is connected
// to the Redux store
const ConnectedComment = enhance(CommentList);

connect是一个高阶组件返回一个高阶组件。这种形式可能会比较迷惑,或者不必要。但是确实是很有用的一个特性。像connect返回的单参数HOC一样,具有Component=>Component这样的特征。这就很容易兼容:

// Instead of doing this...
const EnhancedComponent = connect(commentSelector)(withRouter(WrappedComponent))

// ... you can use a function composition utility
// compose(f, g, h) is the same as (...args) => f(g(h(...args)))
const enhance = compose(
  // These are both single-argument HOCs
  connect(commentSelector),
  withRouter
)
const EnhancedComponent = enhance(WrappedComponent)

有很多第三方库提供这样的compose功能。例如lodash.flowRight,ReduxRamda

公约:起一个方便调试的名称

这些HOC生成的容器组件能够在React调试工具显示出来。为了方便调试,我们要给它们取一个方便的名字,以便调试。
通用的做法就是对原来的组件名称包裹下。例如,高阶组件的名称叫做withSubscription,被包裹的组件叫做CommentList的时候,使用名称WithSubscription(CommentList):

function withSubscription(WrappedComponent) {
  class WithSubscription extends React.Component {/* ... */}
  WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
  return WithSubscription;
}

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

陷阱

如果你是新手,那么就得注意这些问题了:

不能在渲染函数中使用HOCs

React 差异算法通过比较组件是否相同,来决定是否更新它还是直接销毁它。如果在render函数中返回的组件是和之前的组件是相同的,那么就和新的比较下,更新差异;如果不同就直接卸载。
通常情况下不需要想太多,但是如果这里遇到了HOC的话,就要小心了:

render() {
  // A new version of EnhancedComponent is created on every render
  // EnhancedComponent1 !== EnhancedComponent2
  const EnhancedComponent = enhance(MyComponent);
  // That causes the entire subtree to unmount/remount each time!
  return <EnhancedComponent />;
}

这里不是因为性能的问题,而是重新渲染组件,导致组件状态丢失的问题。
所以,应该在组件外使用HOC来确保组件只是生成一次。
若果有时候你需要运用HOC,你也可以在生命周期方法或者构造函数方法中使用。

静态函数必须要手动拷贝过来

有时候在React组件中定义静态方法是很有用的。例如,Relay容器就暴露了一个方法,getFragmnet来加快生成GraphQL fragments。
如果一个组件中使用了HOC,但是源组件又是一个容器组件。这意味着新的组件并没有元组件中的任何静态方法。

// Define a static method
WrappedComponent.staticMethod = function() {/*...*/}
// Now apply an HOC
const EnhancedComponent = enhance(WrappedComponent);

// The enhanced component has no static method
typeof EnhancedComponent.staticMethod === 'undefined' // true

为了解决这个问题,你必须将容器里的静态方法手动拷贝出来:

function enhance(WrappedComponent) {
  class Enhance extends React.Component {/*...*/}
  // Must know exactly which method(s) to copy :(
  Enhance.staticMethod = WrappedComponent.staticMethod;
  return Enhance;
}

所以你又必须知道你具体要拷贝什么函数。你可以使用hoist-non-react-statics来自动拷贝非react的静态代码:

import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
  class Enhance extends React.Component {/*...*/}
  hoistNonReactStatic(Enhance, WrappedComponent);
  return Enhance;
}

另一个解决方法是在容器中分离出静态的方法:

// Instead of...
MyComponent.someFunction = someFunction;
export default MyComponent;

// ...export the method separately...
export { someFunction };

// ...and in the consuming module, import both
import MyComponent, { someFunction } from './MyComponent.js';
refs不会被传递

虽然组件的所有属性都会传递给新的包裹组件。但是不会传递refs。这是因为ref并不是prop,比如key,这些个都会被React单独处理。
如果你面临这样的问题,最好的办法是如何才能不使用ref。可以尝试用props替代。
当时有时候又必须使用refs。例如让input获得焦点。一个解决方法是将refs回调方法通过props传递进去,但是要不同的命名:

function Field({ inputRef, ...rest }) {
  return <input ref={inputRef} {...rest} />;
}

// Wrap Field in a higher-order component
const EnhancedField = enhance(Field);

// Inside a class component's render method...
<EnhancedField
  inputRef={(inputEl) => {
    // This callback gets passed through as a regular prop
    this.inputEl = inputEl
  }}
/>

// Now you can call imperative methods
this.inputEl.focus();

这并不是一个完美的解决方法。我们希望能够有个库来处理ref,而不是手动处理。我们还在寻找优雅的方法。。。


差不多一个月的时间,把两篇guide都学习完了。感觉我是在翻译文章。按道理来说,笔记应该是结合自己的相法。在复习的时候可以重拾自己的理解。我觉得我并没有完全理解React。入门?如果没有项目,我也觉得心虚。所以keep coding~

推荐阅读更多精彩内容

  • 本笔记基于React官方文档,当前React版本号为15.4.0。 1. 安装 1.1 尝试 开始之前可以先去co...
    Awey阅读 5,496评论 19 128
  • 最近看了一本关于学习方法论的书,强调了记笔记和坚持的重要性。这几天也刚好在学习React,所以我打算每天坚持一篇R...
    gaoer1938阅读 674评论 0 5
  • 以下内容是我在学习和研究React时,对React的特性、重点和注意事项的提取、精练和总结,可以做为React特性...
    科研者阅读 5,121评论 2 21
  • 原教程内容详见精益 React 学习指南,这只是我在学习过程中的一些阅读笔记,个人觉得该教程讲解深入浅出,比目前大...
    leonaxiong阅读 1,391评论 0 14
  • ServletContext 生命周期:Web应用被加载时,创建ServletContext对象,服务器关闭或者...
    邹小月阅读 193评论 0 2