写法
一、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 函数组件和类组件有什么区别?
类组件 需要使用 ES6
的 class
语法,并继承 React.Component
或 React.PureComponent
。必须实现一个 render
方法,用于返回 JSX
。通常需要处理 this
绑定问题(例如在事件处理函数中)。通过 this.state
管理状态,使用 this.setState
来更新状态。拥有一整套生命周期方法(如 componentDidMount
、componentDidUpdate
、componentWillUnmount
等)来处理副作用和组件更新逻辑。生命周期方法明确且分散,例如 componentDidMount
、shouldComponentUpdate
、componentDidUpdate
等, 生命周期方法之间可能存在逻辑耦合,需要额外处理副作用和状态同步问题。类组件 对于一些性能优化,如防止不必要的重渲染,可以通过重写 shouldComponentUpdate
或使用 React.PureComponent
。类组件 在 Render BeginWork
递阶段 会获取类的实例, 执行 render
方法。
函数组件 本质上是一个 JavaScript
函数,直接返回 JSX
。语法更简洁,不需要 class
关键字或 render
方法。不存在 this
的问题,所有数据通过函数参数和 hooks
管理。使用 React Hooks
(如 useState
、useReducer
)来管理状态。使用 useEffect
、useLayoutEffect
等 Hook
来处理副作用和模拟生命周期行为。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
浅比较来对比前后 props
和 state
, 任意一个发生变化返回 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
指向的方式有:
- 通过bind 方法进行原地绑定,从而改变this 指向
- 通过创建箭头函数
- 在constructor 中提前对事件进行绑定
- 将事件调用的写法改为箭头函数的形式
3.6 基于 React.Component 类实现 React.PureComponent 的功能, 并实现 Object.is?
实现思路: React PureComponent
继承 React Component
类, 通过添加 isPureReactComponent
标记让内部调度机制知道这是一个纯组件, 并增加了 shouldComponentUpdate
方法, 通过 shallowEqual
浅比较来对比前后 props
和 state
, 任意一个发生变化返回 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)
当 x
和 y
通过 ===
比较相等时, 大多数情况下直接返回 true
, 但是, JavaScript
中 ===
无法区分正零(+0
)和负零(-0
), 因为 +0 === -0
返回 true
, 而有时我们希望区分这两者。区分 +0 === -0
时, 使用 1 / x
和 1 / y
来比较。在 JavaScript
中,1 / 0
得到 Infinity
, 而 1 / (-0)
得到 -Infinity
。只有当正负符号一致时,1 / x === 1 / y
才为 true
, 如果不一致,就说明是 +0
和 -0
, 返回 false
。当 x
和 y
通过 ===
不相等时,可能存在一种特殊情况 NaN
, 当 x
和 y
不相等时,检查它们是否都是 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
访问到组件的属性。