11

修复 React 代码中烦人的 Warning

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=Mzg2NDAzMjE5NQ%3D%3D&%3Bmid=2247487240&%3Bidx=1&%3Bsn=64ceab313813edd2381f777392a15535
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

缺少 Key

qAF3Eri.png!mobileimg

react官方文档是这样描述key的:

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

react的diff算法是把key当成唯一id然后比对组件的value来确定是否需要更新的,所以如果没有key,react将不会知道该如何更新组件。你不传 key 也能用是因为 react 检测到子组件没有 key 后,会默认将数组的索引作为 key。react根据key来决定是销毁重新创建组件还是更新组件,原则是:

  • key相同,组件有所变化,react会只更新组件对应变化的属性。

  • key不同,组件会销毁之前的组件,将整个组件重新渲染。

重复 Key

warning

uameiuv.png!mobileimg

从上面提到的 key 的作用可以知道,如果出现两个相同的 key,则渲染可能出现异常。

错误案例:

bUZ3eif.png!mobileimg

常见的错误是,在使用 antd 的 table 组件时,每个列的 dataIndex 属性同时也会作为 key,注意两个列的 dataIndex 不要相同。

P 标签包含内联元素

warning

VFNzI3v.png!mobileimg

在 HTML5 中,标准制定者重新定义了HTML元素的分类,并根据这一新的分类定义了元素的内容模型(Content Model) -- 对于一个元素而言,哪些子元素是合法的,而哪些子元素是非法的。需要注意的是,HTML5中的这种元素分类与inline、block没有任何关系,任何元素都可以在CSS中被定义为display:inline或者display:block。另外,除了这7大分类,还存在一些较小的分类,如Palpable、Script-Supporting等。

be6NBv.png!mobileimg

Metadata

顾名思义,Metadata元素意指那些定义文档元数据信息的元素 — 其作用包括:影响文档中其它节点的展现与行为、定义文档与其它外部资源之间的关系等。以下元素属于Metadata:base, link, meta, noscript, script, style, template, title。

Flow

所有可以放在body标签内,构成文档内容的元素均属于Flow元素。因此,除了base, link, meta, style, title等只能放在head标签内的元素外,剩下的所有元素均属于Flow元素。

Sectioning

Sectioning意指定义页面结构的元素,具体包含以下四个:article, aside, nav, section。

Heading

所有标题元素属于Heading,也即以下6个元素:h1, h2, h3, h4, h5, h6。

Phrasing

所有可以放在p标签内,构成段落内容的元素均属于Phrasing元素。因此,所有Phrasing元素均属于Flow元素。在HTML5标准文档中,关于Phrasing元素的原始定义为:

Phrasing content is the text of the document, as well as elements that mark up that text at the intra-paragraph level. Runs of phrasing content form paragraphs.

对于这一定义,个人认为不应当使用“text”这一容易引起误解的词,事实上,一个元素即使不是文本,只要能包含在p标签中成为段落内容的一部分,就可以称之为Phrasing元素。比如:audio、video、img、select、input等元素(经测试,这些元素都可以放置在p标签中)。一个不太精确的类比是:HTML5中的Phrasing元素大致就是HTML4中所定义的inline元素。Phrasing元素内部一般只能包含别的Phrasing元素。

Embedded

所有用于在网页中嵌入外部资源的元素均属于Embedded元素,具体包含以下9个:audio, video, img, canvas, svg, iframe, embed, object, math。

Interactive

所有与用户交互有关的元素均属于Interactive元素,包括a, input, textarea, select等。

内容模型(Content Model)

根据以上元素分类,HTML5标准文档定义了任何元素的内容模型 — 对于该元素而言,何种子元素才是合法的。

对于p元素而言,其内容模型为Phrasing, 这意味着p元素只接受Phrasing元素为子元素,而对于像div这样的非Phrasing元素则并不接受。类似的,li元素的内容模型为Flow,因此任何可以放置在body中的元素都可以作为li元素的子元素。

错误案例

iYv6Bbf.png!mobileimg

直接写 html 元素时我们可能会有意识的避免 p 标签包含 div,使用 antd 时有些组件可能会不太注意,比如 Divider 是使用 div 实现的,不能作为 p 标签的子元素。

页面可能正常解析,但不符合语义。这是因为浏览器自带容错机制,对于不规范的写法也能够正确的解析,各浏览器的容错机制不同,所以尽量按规范来写。

Props 类型错误

warning

quInYnv.png!mobile

组件接收的 props 类型与预定义的不符。

错误案例

j6vI3qU.png!mobile

以上的 case 最容易产生这种 warning,当我们定义了一个高阶组件,此组件是对已有 From 组件的一个封装,同时我们额外接收一个 param 参数来做一个其他事情,其他的参数我们要传回 Form。这时如果不做额外的操作,param 参数也会被传入 Form 组件,它是一个意外的参数,这就会让 React 抛出 warning,我们可以做下面的处理:

ZVbAZnQ.png!mobile

componentWillReceiveProps 弃用

warning

fqueMn2.png!mobileimg
  • componentWillMount

  • componentWillReceiveProps

  • componentWillUpdate

这些生命周期经常被误解或滥用,它们的潜在滥用可能会对异步渲染造成更大的问题,未来其会被逐渐弃用,现在使用如果没有加 UNSAFE_ 前缀,则会在控制台抛出错误。

React Fiber 引入了异步渲染,有了异步渲染之后,React 组件的渲染过程是分时间片的,不是一口气从头到尾把子组件全部渲染完,而是每个时间片渲染一点,然后每个时间片的间隔都可去看看有没有更紧急的任务(比如用户按键),如果有,就去处理紧急任务,如果没有那就继续照常渲染。

根据 React Fiber 的设计,一个组件的渲染被分为两个阶段:第一个阶段(也叫做 render 阶段)是可以被 React 打断的,一旦被打断,这阶段所做的所有事情都被废弃,当 React 处理完紧急的事情回来,依然会重新渲染这个组件,这时候第一阶段的工作会重做一遍;第二个阶段叫做 commit 阶段,一旦开始就不能中断,也就是说第二个阶段的工作会稳稳当当地做到这个组件的渲染结束。

两个阶段的分界点,就是 render 函数。render 函数之前的所有生命周期函数(包括 render)都属于第一阶段,之后的都属于第二阶段。在 React v16.3 之前,render 之前的生命周期函数(也就是第一阶段生命周期函数)包括这些:

  • componentWillReceiveProps

  • shouldComponentUpdate

  • componentWillUpdate

  • componentWillMount

  • render

上面提到的滥用,其实就是在这些生命周期中产生了副作用,这些生命周期都应该是纯函数,不应该产生任何副作用。到了 React v16.3,React 干脆引入了一个新的生命周期函数 getDerivedStateFromProps,这个生命周期函数是一个 static 函数,在里面根本不能通过 this 访问到当前组件,输入只能通过参数,对组件渲染的影响只能通过返回值。没错,getDerivedStateFromProps 应该是一个纯函数,React 就是通过要求这种纯函数,强制开发者们必须适应异步渲染。

错误案例

已弃用写法:

class ExampleComponent extends React.Component {
state = {
isScrollingDown: false,
};
componentWillReceiveProps(nextProps) {
if (this.props.currentRow !== nextProps.currentRow) {
this.setState({
isScrollingDown:
nextProps.currentRow > this.props.currentRow,
});
}
}
}

推荐写法:

class ExampleComponent extends React.Component {
state = {
isScrollingDown: false,
lastRow: null,
};
static getDerivedStateFromProps(props, state) {
if (props.currentRow !== state.lastRow) {
return {
isScrollingDown: props.currentRow > state.lastRow,
lastRow: props.currentRow,
};
}
return null;
}
}

getSnapshotBeforeUpdate 无返回值

warning

vaqEba6.png!mobile

如果组件实现了 getSnapshotBeforeUpdate() 生命周期,则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined。

getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()。此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。

错误案例

已弃用写法:

componentWillUpdate(nextProps, nextState) {
if (this.props.list.length < nextProps.list.length) {
this.previousScrollOffset =
this.listRef.scrollHeight - this.listRef.scrollTop;
}
}

componentDidUpdate(prevProps, prevState) {
if (this.previousScrollOffset !== null) {
this.listRef.scrollTop =
this.listRef.scrollHeight -
this.previousScrollOffset;
this.previousScrollOffset = null;
}
}

在上面的示例中,componentWillUpdate用于读取DOM属性。但是,使用异步渲染时,“render”阶段生命周期(如componentWillUpdate和render)和“commit”阶段生命周期(如componentDidUpdate)之间可能会有延迟。如果用户在此期间进行了诸如调整窗口大小的操作,则scrollHeight从中读取的值componentWillUpdate将不准确。

正确用法:

getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.list.length < this.props.list.length) {
return (
this.listRef.scrollHeight - this.listRef.scrollTop
);
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
this.listRef.scrollTop =
this.listRef.scrollHeight - snapshot;
}
}

误用纯函数 Render

warning

AVJjAzU.png!mobileimg

上面我们提到 render 函数也属于 render 阶段的生命周期,所以它一定也要是纯函数,有时候为了方便我们会在 render 函数中做一些状态更改,这种用法是错误的。

错误案例

MZvyiuF.png!mobileimg

上面的案例中,在 render 中根据 hash 值对状态做了更改,正确的用法是这种操作应该在状态初始化时完成,而不是在 render 函数中。

react hot loader

img

这个是 react-hot-loader 的一个 bug,react-hot-loader react-dom 补丁对其进行了修复 https://www.npmjs.com/package/react-hot-loader#hot-loaderreact-dom

安装 @hot-loader/react-dom ,在 webpack 配置中通过 alias 将 @hot-loader/react-dom 指向 react-dom 即可。

【Mobx】observableArray

warning

jyymamf.png!mobileimg

不同于 sort 和 reverse 函数的内置实现,observableArray.sort 和 observableArray.reverse 不会改变数组本身,而只是返回一个排序过/反转过的拷贝。在 MobX 5 及以上版本中会出现警告。推荐使用 array.slice().sort() 来替代。

错误案例

store.data.sort((a, b) => a.status - b.status);

上面的代码不会直接改变 array,推荐下面的写法:

store.data = store.data.slice().sort((a, b) => a.status - b.status);

YZzamaU.jpg!mobile

点赞、在看 支持作者 :heart:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK