<commit_before># 魔鬼藏在细节中

重读了下 React 的文档，发现了很多过去被忽视的细节，所以写下这篇博客。

## [Rendering Elements](https://reactjs.org/docs/rendering-elements.html)

> Unlike browser DOM elements, React elements are plain objects, and are cheap to create. React DOM takes care of updating the DOM to match the React elements.

这里明确说明了 React 元素和 DOM 元素的区别，React 元素仅仅是个 plain object （可以理解为普通的 JSON 对象），所以创建 React 元素消耗的资源非常低。因此维护一颗虚拟 DOM 树比真实 DOM 树廉价得多。

## [Componets and Props](https://reactjs.org/docs/components-and-props.html)

> Conceptually, components are like JavaScript functions. They accept arbitrary inputs (called “props”) and return React elements describing what should appear on the screen.

这里是一个概念的问题，只要能通过属性参数返回相应 React 元素的函数就是一个组件。这个函数一定是纯函数，也就是说外部参数一样，永远得到的是相同结果的组件。这也体现了 React 函数式编程的思想。

所以我们不应该在组件内修改属性，属性必须是只读形式，就像此章节最后提到的 `All React components must act like pure functions with respect to their props.`

## [State and Lifecycle](https://reactjs.org/docs/state-and-lifecycle.html)

> React may batch multiple setState() calls into a single update for performance.
> Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.

当多次调用 setState 方法时，React 会把多次调用方法合并成一个来提高性能。state 的更新可能是异步的，所以不能通过 this.state 来计算下一个 state 的值。这时需要向 setState 传一个回调函数，而不是一个对象。

```js
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}))
```

## [Handling Events](https://reactjs.org/docs/handling-events.html)

三种绑定事件方法的方式：

通过 bind 方式，目前推荐做法

```js
class LoggingButton extends React.Component {
  constructor(props) {
    super(props);
    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}
```

 实验中的 public class fields 语法，官方做法

```js
class LoggingButton extends React.Component {
  // This syntax ensures `this` is bound within handleClick.
  // Warning: this is *experimental* syntax.
  handleClick = () => {
    console.log('this is:', this);
  }
  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}
```

或者使用箭头函数，因为每次作为属性传给子组件都是一个新的箭头函数，会造成子组件重渲染而带来小部分性能问题，谨慎使用

```js
class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // This syntax ensures `this` is bound within handleClick
    return (
      <button onClick={(e) => this.handleClick(e)}>
        Click me
      </button>
    );
  }
}
```

## [Conditional Rendering](https://reactjs.org/docs/conditional-rendering.html)

> Returning null from a component’s render method does not affect the firing of the component’s lifecycle methods. For instance, componentWillUpdate and componentDidUpdate will still be called.

我们可以返回 null 让 React 不渲染元素，但是该组件的生命周期方法仍然会被调用。

## [Lists and Keys](https://reactjs.org/docs/lists-and-keys.html)

> The best way to pick a key is to use a string that uniquely identifies a list item among its siblings.

需要注意的是 key 是 string 类型，且值是唯一的。

> Extracting Components with Keys

对抽离的组件设置 key 属性，而不是在其组件内部的 render 方法中设置。（通常是在 map 函数中使用 key）

> Keys serve as a hint to React but they don’t get passed to your components. If you need the same value in your component, pass it explicitly as a prop with a different name.

key 是不会传给 React 组件，如果组件需要得到 key 的值，必须设置名称不为 key 的属性传给组件。<commit_msg>add Refs and the DOM and Portals<commit_after># 魔鬼藏在细节中

重读了下 React 的文档，发现了很多过去被忽视的细节，所以写下这篇博客。

## [Rendering Elements](https://reactjs.org/docs/rendering-elements.html)

> Unlike browser DOM elements, React elements are plain objects, and are cheap to create. React DOM takes care of updating the DOM to match the React elements.

这里明确说明了 React 元素和 DOM 元素的区别，React 元素仅仅是个 plain object （可以理解为普通的 JSON 对象），所以创建 React 元素消耗的资源非常低。因此维护一颗虚拟 DOM 树比真实 DOM 树廉价得多。

## [Componets and Props](https://reactjs.org/docs/components-and-props.html)

> Conceptually, components are like JavaScript functions. They accept arbitrary inputs (called “props”) and return React elements describing what should appear on the screen.

这里是一个概念的问题，只要能通过属性参数返回相应 React 元素的函数就是一个组件。这个函数一定是纯函数，也就是说外部参数一样，永远得到的是相同结果的组件。这也体现了 React 函数式编程的思想。

所以我们不应该在组件内修改属性，属性必须是只读形式，就像此章节最后提到的 `All React components must act like pure functions with respect to their props.`

## [State and Lifecycle](https://reactjs.org/docs/state-and-lifecycle.html)

> React may batch multiple setState() calls into a single update for performance.
> Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.

当多次调用 setState 方法时，React 会把多次调用方法合并成一个来提高性能。state 的更新可能是异步的，所以不能通过 this.state 来计算下一个 state 的值。这时需要向 setState 传一个回调函数，而不是一个对象。

```js
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}))
```

## [Handling Events](https://reactjs.org/docs/handling-events.html)

三种绑定事件方法的方式：

通过 bind 方式，目前推荐做法

```js
class LoggingButton extends React.Component {
  constructor(props) {
    super(props);
    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}
```

 实验中的 public class fields 语法，官方做法

```js
class LoggingButton extends React.Component {
  // This syntax ensures `this` is bound within handleClick.
  // Warning: this is *experimental* syntax.
  handleClick = () => {
    console.log('this is:', this);
  }
  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}
```

或者使用箭头函数，因为每次作为属性传给子组件都是一个新的箭头函数，会造成子组件重渲染而带来小部分性能问题，谨慎使用

```js
class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // This syntax ensures `this` is bound within handleClick
    return (
      <button onClick={(e) => this.handleClick(e)}>
        Click me
      </button>
    );
  }
}
```

## [Conditional Rendering](https://reactjs.org/docs/conditional-rendering.html)

> Returning null from a component’s render method does not affect the firing of the component’s lifecycle methods. For instance, componentWillUpdate and componentDidUpdate will still be called.

我们可以返回 null 让 React 不渲染元素，但是该组件的生命周期方法仍然会被调用。

## [Lists and Keys](https://reactjs.org/docs/lists-and-keys.html)

> The best way to pick a key is to use a string that uniquely identifies a list item among its siblings.

需要注意的是 key 是 string 类型，且值是唯一的。

> Extracting Components with Keys

对抽离的组件设置 key 属性，而不是在其组件内部的 render 方法中设置。（通常是在 map 函数中使用 key）

> Keys serve as a hint to React but they don’t get passed to your components. If you need the same value in your component, pass it explicitly as a prop with a different name.

key 是不会传给 React 组件，如果组件需要得到 key 的值，必须设置名称不为 key 的属性传给组件。

## [Refs and the DOM](https://reactjs.org/docs/refs-and-the-dom.html)

> If the ref callback is defined as an inline function, it will get called twice during updates, first with null and then again with the DOM element. This is because a new instance of the function is created with each render, so React needs to clear the old ref and set up the new one. You can avoid this by defining the ref callback as a bound method on the class, but note that it shouldn’t matter in most cases.

ref 的回调发生在 `componentDidMount` 和 `componentDidUpdate` 之前。如果 ref 的回调是内联函数，在每次状态更新的时候，会被调用两次，第一次会得到 null 第二次得到 DOM 元素。因为每次 render 的时候，内联函数的内存地址都是不一样的（尽管内部逻辑是一样的），React 会调用一次老的回调，再调用一次新的。如果要避免这种情况，需要保持函数的地址是一致的。

## [Portals](https://reactjs.org/docs/portals.html)

v16 之前的两个隐藏 API 可以实现类似传送门的效果。

- unstable_renderSubtreeIntoContainer
- unmountComponentAtNode

但是通过 Portal 相比上面的两个 API 有一个优点：

> An event fired from inside a portal will propagate to ancestors in the containing React tree, even if those elements are not ancestors in the DOM tree.

在传送门的入口组件触发事件，该事件会通过 React 组件树传送给父组件，即使在 DOM 树中父组件生成的 DOM 节点不是入口组件生成的 DOM 节点的父节点。

{'fraction_non_alphanumeric': 0.09244107949688607, 'fraction_numerical': 0.00024423006472096715, 'mean_word_length': 4.253367543296985, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 39, 'https://': 14, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2462783', 'n_tokens_mistral': 3296, 'n_tokens_neox': 3466, 'n_words': 874}