跳到主要内容

写法

一、Class


1.1 语法

import React, {Component} from 'react';

interface HelloWordProps {}
interface HelloWordState {}

class HelloWord extends Component<HelloWordProps, HelloWordState> {
constructor(props: HelloWordProps) {
super(props);
this.state = {};
}
}

export default HelloWord;

1.2 React Element

{
$$typeof: Symbol(react.element)
key: null
props: {}
ref: null
type: ƒ ClassComponent()
length: 0
name: "ClassComponent"
prototype: Component
constructor: ƒ ClassComponent()
render: ƒ render()
isMounted: undefined
replaceState: undefined
[[Prototype]]: Object
forceUpdate: ƒ (callback)
isReactComponent: {}
setState: ƒ (partialState, callback)
constructor: ƒ Component(props, context, updater)
isMounted: (...)
replaceState: (...)
get isMounted: ƒ ()
get replaceState: ƒ ()
[[Prototype]]: Object
arguments: (...)
caller: (...)
[[FunctionLocation]]: index.js:15
[[Prototype]]: ƒ Component(props, context, updater)
[[Scopes]]: Scopes[4]
_owner: null
_store: {validated: false}
_self: null
_source: null
}

二、Function


2.1 语法

声明式语法
import React from 'react';

interface HelloWordProps {}

function HelloWord(props: HelloWordProps): JSX.Element {
return <div />;
}

export default HelloWord;
表达式语法
import React, {FunctionComponent} from 'react';

interface HelloWordProps {}

const HelloWord: FunctionComponent<HelloWordProps> = () => <div />;

export default HelloWord;

2.2 React Element

{
$$typeof: Symbol(react.element)
key: null
props: {}
ref: null
type: ƒ FunctionComponent()
_owner: null
_store: {validated: false}
_self: null
_source: null
[[Prototype]]: Object
}

三、问题


3.1 函数组件和类组件有什么区别?

类组件 需要使用 ES6class 语法,并继承 React.ComponentReact.PureComponent。必须实现一个 render 方法,用于返回 JSX。通常需要处理 this 绑定问题(例如在事件处理函数中)。通过 this.state 管理状态,使用 this.setState 来更新状态。拥有一整套生命周期方法(如 componentDidMountcomponentDidUpdatecomponentWillUnmount 等)来处理副作用和组件更新逻辑。生命周期方法明确且分散,例如 componentDidMountshouldComponentUpdatecomponentDidUpdate 等, 生命周期方法之间可能存在逻辑耦合,需要额外处理副作用和状态同步问题。类组件 对于一些性能优化,如防止不必要的重渲染,可以通过重写 shouldComponentUpdate 或使用 React.PureComponent类组件Render BeginWork 递阶段 会获取类的实例, 执行 render 方法。

函数组件 本质上是一个 JavaScript 函数,直接返回 JSX。语法更简洁,不需要 class 关键字或 render 方法。不存在 this 的问题,所有数据通过函数参数和 hooks 管理。使用 React Hooks(如 useStateuseReducer)来管理状态。使用 useEffectuseLayoutEffectHook 来处理副作用和模拟生命周期行为。useEffect 可以同时处理挂载、更新和卸载时的逻辑,代码更具模块化。而函数组件也可以使用 memo 或者 useMemo 缓存函数组件来达到效果。函数组件Render BeginWork 递阶段 , 会直接执行函数。每一次的更新都会执行函数。因此, 函数中的所有状态都会保存到 函数组件 Fiber.memorizedState 中。

3.2 super() 和 super(props) 有什么区别?

ES6 的类继承中,super() 用于调用父类(即 React.Component)的构造函数,而 super(props) 则是将当前组件的 props 传递给父类构造函数。两者的主要区别在于是否把 props 传入父类构造函数,从而影响到组件内 this.props 的初始化。

super() 不传递 props,父类构造函数不会收到 props 参数,因此在构造函数内访问 this.props 可能会得到 undefined

super(props)props 传递给父类构造函数,使得在父类(React.Component)的构造函数中能够正确初始化 this.props

ES5 继承 实质上是先创建子类的实例对象,然后再将父类的方法添加到 this上(父类.call(this)); ES6 继承 子类没有自己的 this 对象, 只能继承父类 this 对象, 然后对其进行加工。子类通过调用 super 关键字, 初始化父类, 将父类中的 this 继承给子类, 没有调用 super , 子类就得不到 this 对象, 因此, 在子类 constructor 中, 必须先调用 super, 才能引用 this

3.3 函数组件中如何实现像 PureComponent 类似的功能?

在函数组件中,可以使用 React.memo 来实现类似于 PureComponent 的优化效果。React.memo 是一个高阶组件,它会对组件的 props 进行浅比较,只有当 props 发生变化时才会重新渲染组件,从而避免不必要的渲染。React.memo 默认为浅比较前后 prop, 检测是否发生变化。另外, 可以传入自定义比较函数。通过 shallowEqual 浅比较来对比前后 propsstate, 任意一个发生变化返回 true, 继续更新组件。shallowEqual 浅比较的逻辑为: 该函数首先判断引用是否相同,然后判断对象类型和 null 情况,接着对比两个对象的键数和每个键对应的值,模拟了 React 内部的浅比较逻辑。比较每个键值时, 通过 Object.is 来实现。

import React from "react";

function shallowEqual(objA, objB) {
if (Object.is(objA, objB)) {
return true;
}

if (
typeof objA !== "object" ||
objA === null ||
typeof objB !== "object" ||
objB === null
) {
return false;
}

const keysA = Object.keys(objA);
const keysB = Object.keys(objB);

if (keysA.length !== keysB.length) {
return false;
}

for (let i = 0; i < keysA.length; i++) {
if (
!Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
!Object.is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}

return true;
}

function memo(Component, areEqual) {
class MemoizedComponent extends React.Component {
shouldComponentUpdate(nextProps) {
const compare = areEqual || shallowEqual;
return !compare(this.props, nextProps);
}

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

MemoizedComponent.displayName = `Memo(${Component.displayName || Component.name})`;
return MemoizedComponent;
}

export default memo;

3.4 调用 super(props) 的目的是什么?

ES6 的类继承中,super() 用于调用父类(即 React.Component)的构造函数,而 super(props) 则是将当前组件的 props 传递给父类构造函数。两者的主要区别在于是否把 props 传入父类构造函数,从而影响到组件内 this.props 的初始化。

super() 不传递 props,父类构造函数不会收到 props 参数,因此在构造函数内访问 this.props 可能会得到 undefined

super(props)props 传递给父类构造函数,使得在父类(React.Component)的构造函数中能够正确初始化 this.props

ES5 继承 实质上是先创建子类的实例对象,然后再将父类的方法添加到 this上(父类.call(this)); ES6 继承 子类没有自己的 this 对象, 只能继承父类 this 对象, 然后对其进行加工。子类通过调用 super 关键字, 初始化父类, 将父类中的 this 继承给子类, 没有调用 super , 子类就得不到 this 对象, 因此, 在子类 constructor 中, 必须先调用 super, 才能引用 this

3.5 React 类组件修改 this 指向的方式有哪些?

答: 修改 this 指向的方式有:

  1. 通过bind 方法进行原地绑定,从而改变this 指向
  2. 通过创建箭头函数
  3. 在constructor 中提前对事件进行绑定
  4. 将事件调用的写法改为箭头函数的形式

3.6 基于 React.Component 类实现 React.PureComponent 的功能, 并实现 Object.is?

实现思路: React PureComponent 继承 React Component, 通过添加 isPureReactComponent 标记让内部调度机制知道这是一个纯组件, 并增加了 shouldComponentUpdate 方法, 通过 shallowEqual 浅比较来对比前后 propsstate, 任意一个发生变化返回 true, 继续更新组件。shallowEqual 浅比较的逻辑为: 该函数首先判断引用是否相同,然后判断对象类型和 null 情况,接着对比两个对象的键数和每个键对应的值,模拟了 React 内部的浅比较逻辑。比较每个键值时, 通过 Object.is 来实现。

function is(x,y){
if(x === y){
return x !== 0 || y !== 0 || 1 / x === 1 / y;
}else{
return x !== x && y !== y;
}
}

Object.is(x,y)xy 通过 === 比较相等时, 大多数情况下直接返回 true, 但是, JavaScript=== 无法区分正零(+0)和负零(-0), 因为 +0 === -0 返回 true, 而有时我们希望区分这两者。区分 +0 === -0 时, 使用 1 / x1 / y 来比较。在 JavaScript 中,1 / 0 得到 Infinity, 而 1 / (-0) 得到 -Infinity。只有当正负符号一致时,1 / x === 1 / y 才为 true, 如果不一致,就说明是 +0-0, 返回 false。当 xy 通过 === 不相等时,可能存在一种特殊情况 NaN, 当 xy 不相等时,检查它们是否都是 NaN, 如果是,则返回 true

function shallowEqual(objA, objB) {
if (Object.is(objA, objB)) {
return true;
}

if (
typeof objA !== "object" ||
objA === null ||
typeof objB !== "object" ||
objB === null
) {
return false;
}

const keysA = Object.keys(objA);
const keysB = Object.keys(objB);

if (keysA.length !== keysB.length) {
return false;
}

for (let i = 0; i < keysA.length; i++) {
if (
!Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
!Object.is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}

return true;
}

shallowEqual(objA, objB) 浅比较 该函数首先判断引用是否相同,然后判断对象类型和 null 情况,接着对比两个对象的键数和每个键对应的值

import { Component } from "react";

class PureComponent extends Component {
constructor(props: any) {
super(props);
this.state = {
count: 0,
};
}

handleClick = () => {
this.setState({
count: this.state.count + 1,
});
};

shouldComponentUpdate(nextProps, nextState) {
return (
!shallowEqual(nextProps, this.props) ||
!shallowEqual(nextState, this.state)
);
}

render() {
console.log("PureComponent 渲染");
return (
<div>
<h1>自定的 PureComponent</h1>
{this.state.count}
<button onClick={()=> this.handleClick()}>改变</button>
</div>
);
}
}

export default PureComponent;

3.7 如果 React.Component 没有显示声明 constructor, 会发生什么?

当你在类组件中没有显式定义 constructor 时, JavaScript 会自动生成一个默认构造函数, 等同于:

constructor(...args) {
super(...args);
}

父类 React.Component 的构造函数会接收到传入的 props, 所以你依然可以通过 this.props 访问到组件的属性。