Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 运行过程 #99

Open
coconilu opened this issue Oct 22, 2018 · 0 comments
Open

React 运行过程 #99

coconilu opened this issue Oct 22, 2018 · 0 comments

Comments

@coconilu
Copy link
Owner

coconilu commented Oct 22, 2018

概述

  1. React 编程思路
    • 创建组件
    • 挂载组件
  2. 组件的基本特性
    • 维护一些状态,状态更新的时候自动渲染组件
    • 监听组件的交互事件
    • 了解组件的生命周期
  3. 模板引擎相关
    • React和HTML DOM属性的区别
    • 条件渲染
    • 列表渲染
    • Refs
    • fragment
  4. 复合组件
    • 状态提升
    • 组合
    • Context
    • portals
    • 高阶组件
    • Render Props

React 编程思路

可以简单概括为两步:

  1. 创建组件
  2. 挂载组件

1. 创建组件

创建组件的语法有两种:使用JSX或者React.createElement()

JSX是React.createElement()的语法糖,通过Babel来转化的,一般脚手架工具都会配置好Babel,我们可以直接使用JSX,另外我们需要import React,不然转化为React.createElement()的时候会报错找不到React

创建组件的方法有两种:使用函数或者类

1. 使用函数的话,需要返回JSX(会转换为React.createElement()
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
2. 使用类的话,需要提供render函数,并返回JSX
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

在React里,组件有三种类型:

  1. 普通节点,标签名首字母需要小写
  2. React component,标签名首字母需要大写
  3. React fragment,组件一般仅会返回根元素(根元素会包含其它的子元素),而fragment可以返回多个元素而不需要根元素包裹

fragment相当于VueJS的<template>标签

2. 挂载组件

挂载很简单,仅需要ReactDOM.render()这个API即可。

完整API如下:

ReactDOM.render(
  element,
  container,
  [callback]
)

element,指的是React元素
container,指的是挂载React元素的容器,如果container原本还有子元素的话,这些子元素会被去掉
callback,回调函数是可选的。如果你提供了,程序会在渲染或更新之后执行这个函数

组件的基本特性

一般情况下,给组件添加的属性会添加到props:函数组件是第一个入参(一般会命名为props),类组件通过this.props访问。

props有一个严格的规则(只读性):所有的React组件必须像纯函数那样使用它们的props。

所以,我们想通过props重新渲染组件的话,仅能通过ReactDOM.render()再次挂载了。

如果你还需要:

1. 维护一些状态,状态更新的时候自动渲染组件

通过类组件,我们可以使用一些特性,例如局部状态、生命周期钩子。

通过在类构造函数里初始化状态:

constructor(props) {
  super(props);
  this.state = {date: new Date()};
}

如果需要更新状态的话,我们可以使用API:this.setState()。有三件事需要注意:

1. 不要直接更新状态

this.state.comment = 'Hello'; // Wrong
this.setState({comment: 'Hello'}); // Correct

2. 状态更新可能是异步的

对于异步更新的情况,可以给this.setState()传递一个回调,第一个参数为先前的状态,第二个参数为props

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

3. 状态更新合并

this.setState()仅更新参数里提及的状态,不影响其它的。
比如this.state = {a:1, b:2}
当我们更新状态this.setState({a: 3})
state的状态将会更新为{a:3, b:2}

底层代码使用的API应该是Object.assign()

2. 监听组件的交互事件

React 元素的事件处理和 DOM元素的很相似。但是语法上有一些不同:

  1. React事件绑定属性的命名采用驼峰式写法,而不是小写
  2. 如果采用 JSX 的语法你需要传入一个函数作为事件处理函数,而不是一个字符串(DOM元素的写法)

值得注意的还有另外三件事:

  1. 事件处理函数的入参e是一个合成事件,React解决了跨浏览器的兼容性问题
  2. 事件处理函数的this如果需要绑定到组件实例上的话,可以使用箭头函数或者Function.prototype.bind()
  3. 如果需要向事件处理函数传递参数,可以参考下面的例子:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

3. 了解组件的生命周期

每一个组件都有几个你可以重写以让代码在处理环节的特定时期运行的“生命周期方法”。
方法中带有前缀 will 的在特定环节之前被调用,而带有前缀 did 的方法则会在特定环节之后被调用。

借助网上的一张图,方便理解:

image

装配阶段

1. constructor(props)

构造函数是初始化状态的合适位置。若不初始化状态且不绑定方法,那也不需要为React组件定义一个构造函数。

2. static getDerivedStateFromProps(nextProps, prevState)

组件实例化后和接受新属性时将会调用getDerivedStateFromProps。它应该返回一个对象来更新状态,或者返回null来表明新属性不需要更新任何状态。

注意,如果父组件导致了组件的重新渲染,即使属性没有更新,这一方法也会被调用。如果你只想处理变化,你可能想去比较新旧值。

调用this.setState() 通常不会触发 getDerivedStateFromProps()。

3. componentWillMount() / UNSAFE_componentWillMount()

UNSAFE_componentWillMount()在装配发生前被立刻调用。其在render()之前被调用,因此在这方法里同步地设置状态将不会触发重渲。

避免在该方法中引入任何的副作用或订阅。对于这些使用场景,我们推荐使用constructor()来替代。

这一生命周期之前叫做componentWillMount。这一名字在17版前都有效。

4. render()

当被调用时,其应该检查this.propsthis.state并返回以下类型中的一个:

  • React元素。 通常是由 JSX 创建。该元素可能是一个原生DOM组件的表示,如<div />,或者是一个你定义的合成组件。
  • 字符串和数字。 这些将被渲染为 DOM 中的 text node。
  • Portals。 由 ReactDOM.createPortal 创建。
  • null。 什么都不渲染。
  • 布尔值。 什么都不渲染。(通常存在于 return test && <Child />写法,其中 test 是布尔值。)

render()函数应该纯净,意味着其不应该改变组件的状态,其每次调用都应返回相同的结果,同时不直接和浏览器交互。若需要和浏览器交互,将任务放在componentDidMount阶段或其他的生命周期方法。

5. componentDidMount()

componentDidMount()在组件被装配后立即调用。初始化使得DOM节点应该进行到这里。若你需要从远端加载数据,这是一个适合实现网络请求的地方。在该方法里设置状态将会触发重渲。

这一方法是一个发起任何订阅的好地方。如果你这么做了,别忘了在componentWillUnmount()退订。

在这个方法中调用setState()将会触发一次额外的渲染,但是它将在浏览器刷新屏幕之前发生。谨慎使用这一模式,因为它常导致性能问题。

更新阶段

1. componentWillReceiveProps(nextProps) / UNSAFE_componentWillReceiveProps(nextProps)

推荐使用getDerivedStateFromProps而不是UNSAFE_componentWillReceiveProps

调用this.setState通常不会触发UNSAFE_componentWillReceiveProps

这一生命周期之前叫做componentWillReceiveProps。这一名字在17版前都有效。

2. static getDerivedStateFromProps()

同前面的static getDerivedStateFromProps()

3. shouldComponentUpdate(nextProps, nextState)

当接收到新属性或状态时,shouldComponentUpdate() 在渲染前被调用。默认为true。该方法并不会在初始化渲染或当使用forceUpdate()时被调用。

当他们状态改变时,返回false 并不能阻止子组件重渲。

当前,若shouldComponentUpdate()返回false,而后UNSAFE_componentWillUpdate(),render(), 和 componentDidUpdate()将不会被调用。注意,在未来React可能会将shouldComponentUpdate()作为一个线索而不是一个严格指令,返回false可能仍然使得组件重渲。

4. componentWillUpdate(nextProps, nextState) / UNSAFE_componentWillUpdate(nextProps, nextState)

当接收到新属性或状态时,UNSAFE_componentWillUpdate()会在渲染前被立即调用。在更新发生前,使用该方法是一次准备机会。该方法不会在初始化渲染时调用。

注意你不能在这调用this.setState(),若你需要更新状态响应属性的调整,使用getDerivedStateFromProps() 代替。

这一生命周期之前叫做componentWillUpdate。这一名字在17版前都有效。

5. render()

同前面的render()

6. getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate()在最新的渲染输出提交给DOM前将会立即调用。它让你的组件能在当前的值可能要改变前获得它们。这一生命周期返回的任何值将会 作为参数被传递给componentDidUpdate()

7. componentDidUpdate(prevProps, prevState)

componentDidUpdate()会在更新发生后立即被调用。该方法并不会在初始化渲染时调用。

卸载

componentWillUnmount()

componentWillUnmount()在组件被卸载和销毁之前立刻调用。可以在该方法里处理任何必要的清理工作,例如解绑定时器,取消网络请求,清理任何在componentDidMount环节创建的DOM元素。

错误处理

componentDidCatch()

错误边界是React组件,并不是损坏的组件树。错误边界捕捉发生在子组件树中任意地方的JavaScript错误,打印错误日志,并且显示回退的用户界面。错误边界捕捉渲染期间、在生命周期方法中和在它们之下整棵树的构造函数中的错误。

模板引擎相关

条件渲染

因为render函数返回null、undefined和布尔值(包括true、false)的话,React什么都不渲染。

注意0并不能阻止渲染,所以直接判断数组的length属性并不能达到目的,可以判断length属性是否大于0来达到目的

所以我们可以通过这个特性来达到根据条件来渲染组件的目的。

方式:

  1. if 语句
  2. 短路逻辑运算符:&&
  3. 三目运算符

列表渲染

仅需要在JSX里通过<Parent>{ components }</Parent>就可以渲染列表,components应该是一个数组,所以我们一般会通过Array.prototype.map()去生成components。

JSX会去解析components并把它们按顺序插入父组件中。

Keys

Keys可以在DOM中的某些元素被增加或删除的时候帮助React识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。

元素的key只有在它和它的兄弟节点对比时才有意义。所以数组元素中使用的key在其兄弟之间应该是独一无二的。然而,它们不需要是全局唯一的。

其它

React和HTML DOM属性的区别

checked属性

标签type属性值为checkbox或radio时,支持checked属性。
defaultChecked这是非受控组件的属性,用来设定对应组件首次加载时是否选中状态。

类名属性

在React中,使用className属性指定一个CSS类。

dangerouslySetInnerHTML函数

dangerouslySetInnerHTML是React提供的替换浏览器DOM中的innerHTML接口的一个函数。

htmlFor

因为在javascript中for是一个保留字,所以React元素使用 htmlFor代替。

onChange函数

onChange事件处理函数的表现正如你所期望的:无论form表单何时发生变化,这个事件都会被触发。

selected
组件支持selected属性。你可以使用该属性设定组件是否选中的状态。这对构建受控组件很有用。
style属性

style属性接受一个键为小驼峰命名法命名的javascript对象作为值,而不是像css字符串。

suppressContentEditableWarning

一般来说,当一个拥有子节点的元素被标记为contentEditable时,React会发出一个警告信息,因为此时contentEditable是无效的。

value

<input><textarea> 组件都支持value属性。

<select>支持使用value属性来设置选中项。
defaultValue属性对应的是非受控组件的属性,用来设置组件第一次加载时的值。

Refs

ref可以引用到React渲染后的元素,它就是普通的HTML元素,所以它可以使用原生的HTML元素的方法。

在React组件挂载后或更新后,ref才可以被正确引用,所以一般会使用在声明周期函数:componentDidMoutcomponentDidUpdate

基本使用方式:

class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  componentDidMount() {
    this.textInput.current.focusTextInput();
  }

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

Fragments

React 中一个常见模式是为一个组件返回多个元素。Fragments 可以让你聚合一个子元素列表,并且不在DOM中增加额外节点。

使用方式:

render() {
  return (
    <>
      <ChildA />
      <ChildB />
      <ChildC />
    </>
  );
}

看起来像一对空的JSX标签,fragment相当于VueJS的<template>标签

复合组件

前面提到的几乎是针对单组件的情况。但是在构建大型应用的时候,使用复合组件可以节省很多时间。

复合关系包括父子组件关系、兄弟组件关系。

状态提升

使用 react 经常会遇到几个组件需要共用状态数据的情况。这种情况下,我们最好将这部分共享的状态提升至他们最近的父组件当中进行管理。

步骤:

  1. 提取公共状态到父组件
  2. 父组件把公共状态通过props传递给子组件
  3. 父组件把修改公共状态的方法也通过props传递给子组件
  4. 子组件在恰当时机执行修改公共状态的方法,会触发父组件更新状态,重新渲染组件

组合而不是继承

React 具有强大的组合模型,我们建议使用组合而不是继承来复用组件之间的代码。

  1. 可以通过props传递JSX(React.createElement())给组件。
  2. 可以通过props.children传递嵌套在组件中的子元素。

Context

Context 设计目的是为共享那些被认为对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。

使用方式:

const {Provider, Consumer} = React.createContext(defaultValue);

<Provider value={/* some value */}>
<Consumer>
  {value => /* render something based on the context value */}
</Consumer>

Portals

Portals 提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的方式。

使用方式:

ReactDOM.createPortal(child, container)

第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或碎片。
第二个参数(container)则是一个 DOM 元素。

高阶组件

就跟高阶函数一样,函数可以被作为参数传入另一个函数,函数还可以返回另一个函数。高阶组件的意思就是,组件可以作为props传入另一个组件,组件还可以返回另一个组件。

高阶组件就是一个没有副作用的纯函数。高阶组件是通过将原组件 包裹(wrapping) 在容器组件(container component)里面的方式来 组合(composes) 使用原组件。

高阶组件有以下约定:

  1. 将不相关的props属性传递给包裹组件
  2. 最大化使用组合
  3. 包装显示名字以便于调试

还有以下注意事项:

  1. 不要在render函数中使用高阶组件
  2. 必须将静态方法做拷贝——hoist-non-react-statics库可以帮到你
  3. 高阶组件可以传递除了Refs的所有props属性给包裹的组件——可以使用React.forwardRef 的 API 来解决这一问题

Render Props

前面提到,可以通过props传递数值、函数、组件给子组件。

不仅如此,props还可以传递返回组件的函数,一般使用箭头函数表示。这就是Render Props。

这对于降低组件间的耦合度是很有帮助的。父组件不需要知道具体的子组件,子组件也不必关系父组件怎么引用它。

更具体地说,render prop 是一个组件用来了解要渲染什么内容的函数 prop。

参考

React 官方文档
React 中文文档
RUNOOB的React 教程
React 学习笔记
react学习笔记
React学习笔记

@coconilu coconilu mentioned this issue Jun 23, 2020
68 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant