修复 React 代码中烦人的 Warning
The following article is from code秘密花园 Author ConardLi
(给前端大全加星标,提升前端技能)
作者:code秘密花园 公号 / ConardLi
缺少 Key
react官方文档是这样描述key的:
Keys可以在DOM中的某些元素被增加或删除的时候帮助React识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。
react的diff算法是把key当成唯一id然后比对组件的value来确定是否需要更新的,所以如果没有key,react将不会知道该如何更新组件。你不传 key 也能用是因为 react 检测到子组件没有 key 后,会默认将数组的索引作为 key。react根据key来决定是销毁重新创建组件还是更新组件,原则是:
key相同,组件有所变化,react会只更新组件对应变化的属性。 key不同,组件会销毁之前的组件,将整个组件重新渲染。
重复 Key
warning
从上面提到的 key 的作用可以知道,如果出现两个相同的 key,则渲染可能出现异常。
错误案例:
常见的错误是,在使用 antd 的 table 组件时,每个列的 dataIndex 属性同时也会作为 key,注意两个列的 dataIndex 不要相同。
P 标签包含内联元素
warning
在 HTML5 中,标准制定者重新定义了HTML元素的分类,并根据这一新的分类定义了元素的内容模型(Content Model) -- 对于一个元素而言,哪些子元素是合法的,而哪些子元素是非法的。需要注意的是,HTML5中的这种元素分类与inline、block没有任何关系,任何元素都可以在CSS中被定义为display:inline或者display:block。另外,除了这7大分类,还存在一些较小的分类,如Palpable、Script-Supporting等。
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元素的子元素。
错误案例
直接写 html 元素时我们可能会有意识的避免 p 标签包含 div,使用 antd 时有些组件可能会不太注意,比如 Divider 是使用 div 实现的,不能作为 p 标签的子元素。
页面可能正常解析,但不符合语义。这是因为浏览器自带容错机制,对于不规范的写法也能够正确的解析,各浏览器的容错机制不同,所以尽量按规范来写。
Props 类型错误
warning
组件接收的 props 类型与预定义的不符。
错误案例
以上的 case 最容易产生这种 warning,当我们定义了一个高阶组件,此组件是对已有 From 组件的一个封装,同时我们额外接收一个 param 参数来做一个其他事情,其他的参数我们要传回 Form。这时如果不做额外的操作,param 参数也会被传入 Form 组件,它是一个意外的参数,这就会让 React 抛出 warning,我们可以做下面的处理:
componentWillReceiveProps 弃用
warning
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
如果组件实现了 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
上面我们提到 render 函数也属于 render 阶段的生命周期,所以它一定也要是纯函数,有时候为了方便我们会在 render 函数中做一些状态更改,这种用法是错误的。
错误案例
上面的案例中,在 render 中根据 hash 值对状态做了更改,正确的用法是这种操作应该在状态初始化时完成,而不是在 render 函数中。
react hot loader
这个是 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
不同于 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);
1、推荐一个零配置开箱即用的React/Vue应用自动化构建脚手架,不强大你来找我
觉得本文对你有帮助?请分享给更多人
推荐关注「前端大全」,提升前端技能
点赞和在看就是最大的支持❤️