React开发踩坑总结(持续更新)

1,251 阅读4分钟

this迷失

由于React开发的灵活性,在组件属性传递时,操作函数也常常作为属性被传递进去。由于未使用使用箭头函数造成函数在执行时根据上下文确定this指针的值,常常造成this is undefined的问题。

import React, { Component } from 'react';

export default Button extends Component {
	handleClick = () => {
		const { onClick } = this.props
		if (onClick) {
			onClick()
		}
	}
	render () {
		return <button onClick={onClick}>确认</button>
	}
}
// onClick作为属性传递是特别需要注意,当函数内部使用this指针的值时,有可能会存在this迷失的问题

DOM XSS

在前端的server进行html文件的拼装时,尤其是需要函数写入script标签的内容时需要特别注意,需要对写入的内容做校验,如果存在html语义标签时可能存在DOM XSS的风险。

const jsContent = fetchFromServer()
// ejs进行内容写入
<script>
window.globalVar = JSON.stringify(jsContent)
</script>
// 此时需要注意如果jsContent包含html语义标签时可能会造成漏洞,如jsContent = {name: '<script>测试漏洞</script>'}, 组装好的html文件放回前端时就会执行报错

// 使用htmlEncode对html语义内容进行encode
const htmlEncode = (html) => {
	return html.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

Safari文本选择高度计算问题

Safari浏览器中光标拖拽选择文本非首行包含起始文本内容时,在使用原生的window.getSelection()方法获取选区时,并通过RangegetBoundingClientRect()方法获取选区高度时有问题的,是正常选区高度的两倍(暂时没有找到相关文档有说明过该问题,如果有哪位大佬知道该问题产生的原因可留言告知)。目前推测原因是因为在选取非首行首个文本内容时会默认从上一行的末尾开始选择,因此造成高度是两行。

Safari
Safari浏览器的选择高度和所选内容高度是正常高度的两倍,且选区内容有两部分。在ChromeFireFox选择相同的内容表现则不是如此。
Chrome
这种浏览器差异造成了开发时高度位置有误,暂时没有找到特别通用的解决方案,只是暴力的对Safari浏览器进行单独的处理。

const getSelectionRectHeight = () => {
	const isSafariBrowser = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent)
	const nativeSelection = window.getSelection()
	if (nativeSelection.rangeCount) {
		const nativeSelectionRange = nativeSelection.getRangeAt(0)
		if (isSafariBrowser) {
			const nativeSelectionRects = nativeSelectionRange.getClientRects()
			if (nativeSelectionRects.length === 2) {
				const hasZeroRect = [...nativeSelectionRects].some(rect => !rect.width)
				return hasZeroRect ? [...nativeSelectionRects].find(rect => !!rect.width) : nativeSelectionRange.getBoundingClientRect()
			}
		}
		return nativeSelectionRange.getBoundingClientRect()
	}
}

react组件销毁单例内容的坑

项目中使用一个全局的svg的图标库,因此封装了一个svg图标组件,而图标组件在使用时销毁报错,原因时this.svgElethis.styleEle并未在document.bodydocument.head内不是其子元素因此使用removeChild函数时会报错。

// SVGSymbols.tsx组件,全局注入svg库与样式文件
import React from 'react';
// 获取浏览器全局globalThis并在其上挂载__svg__symbols__loaded属性标识是否已经全局加载过svg库
import LoadedManager from './loaded-manager'
import SVG_CONTENT from '!!raw-loader!svg-symbols.svg';
import STYLE_CONTENT from '!!raw-loader!svg-symbols.css';

export default SVGSymbols extends React.PureComponent {
	private svgContainerEle = document.createElement('div');
	private styleContainerEle = document.createElement('style');
	private generateSvgContent () {
		this.svgContainerEle.setAttribute('data-role', 'svg-symbols-container');
		this.svgContainerEle.insertAdjacentHTML('afterbegin', SVG_CONTENT);
    document.body.insertBefore(this.svgContainerEle, document.body.firstChild);
	}
	private generateStyleContent () {
		this.styleContainerEle.setAttribute('data-role', 'svg-symbols-css');
		this.styleContainerEle.insertAdjacentHTML('afterbegin', STYLE_CONTENT);
		document.head.appendChild(this.styleContainerEle);
	}
	componentDoneMount () {
		if (!LoadedManager.isLoaded) {
			this.generateSvgContent();
			this.generateStyleContent();
		}
	}
	componentWillUnmount () {
		if (LoadedManager.isLoaded) {
			// 此处有埋坑
			document.body.removeChild(this.svgContainerEle);
			document.head.removeChild(this.styleContainerEle);
		}
	}
	render () {
		return null;
	}
}

// SVGIcon.tsx组件加载生成对应的svg图标
import React from 'react';
import SVGSymbols from './SVGSymbols'

export eunm SVGIconSize {
	large = 'xl',
	  middle = 'l',
	  small = 'm'
}
interface SVGIconProps extends React.HTMLAttributes<HTMLSpanElement> {
  type?: string
  size?: SvgSize
}
export default SVGIcon extends React.PureComponent<SVGIconProps> {
	render () {
		const {type} = this.props
		return (
			<>
				<SVGSymbols />
				<span>
					<svg>
						<use xlinkHref={`#${type}`} />
					</svg>
				</span>
			</>
		)
	}
}

该组件看起来没有问题但是如果使用map函数生成全局过个SVGIcon进行使用时,在组件进行销毁时,我们知道其中一个SVGSymbols组件的componentWillUnmount会页面中移除全局插入的svg内容,那么其他组件的componentWillUnmount将会报错,因为this.svgContainerElethis.styleContainerEle元素并未插入到页面中,因此其也不是bodyhead的子元素,因此调用removeChild函数会报错。

// 增加判断逻辑进行remove
if (document.body.contains(this.styleContainerEle)) {
	document.body.removeChild(this.styleContainerEle);
}
if (document.head.contains(this.styleContainerEle)) {
	document.head.removeChild(this.styleContainerEle);
}

优化可以使用一个LoadedManager进行单例模式的注入与删除而无需在单写一个SVGSymbols组件。