1.JSX
直观上看,JSX是将HTML直接嵌入了JS代码中。它让前端实现真正意义上的组件化,通过函数抽离或IIFE(立即执行函数)可以调试JSX。
JSX会被编译为React.createElement,而React.createElement的底层逻辑是无法运行js的,只能渲染一个结果。因此,JSX中除了js表达式不能直接写js语法。JSX只是函数调用和表达式的语法糖。
2.setState
setState方法并不总是能够立刻更新组件,它可能会延迟更新,这样在通过this.state读取内容时,就有可能获取不到最新的状态值。这样的设计是为了通过延迟更新获得更好的性能。
所谓的延迟更新并不是针对所有情况的,实际上,在React控制之内的事件处理过程中,setState不会同步更新this.state;而对于控制之外的情况,会同步执行。
onClick () {
this.setState({})
}
componentDidMount () {
document.querySelectorAll('#btn').addEventListener('click', this.onClick)
}
render() {
return (
<>
<button id="btn">out</button>
<button onClick={this.onClick}>in</button>
</>
)
}
在id为btn的button上绑定的事件,是在componentDIdMount方法中通过添加事件监听完成更新的,这是脱离于react事件之外的事件,因此它是同步的;而button上绑定的事件处理函数对应的setState是异步更新的。
promise化setState解决状态间的回调地狱问题:
const setStatePromise = (me, state) => {
new Promise(resolve => {
me.setState(state, () => {
resolve()
})
})
}
3.合成事件
react中事件机制并不是原生的那一套,没有绑定在原生DOM上,大多数事件绑定在document上(除了少数不会冒泡到document的事件,如video等)。触发的事件也是对原生事件的包装,并不是原生event对象。合成事件对象将会被重用,在调用事件回调函数后,合成事件对象上的所有属性都将会被废弃,可以大大节省内存,而不会频繁地创建和销毁事件对象。
这样的事件系统设计,无疑会使性能有所提升,但会引发几个潜在现象:
-
异步访问事件对象
function handleClick (e) { console.log(e) e.persist() setTimeout(() => { console.log(e) }, 0) }
当所有事件处理函数被调用之后,其所有属性都会被置空。如果你需要在事件处理函数运行之后获取事件对象的属性,你需要调用 e.persist()持久化合成事件。
-
阻止原生事件冒泡
handleClick = e => { console.log('click') e.nativeEvent.stopImmediatePropagation() }
直接使用e.stopPropagation不能阻止原生事件冒泡,因为事件早已经冒泡到了document上,react在事件冒泡到document上时才能够处理事件。可以通过合成事件上的nativeEvent属性访问原生事件,原生事件上的stopImmediatePropagation方法除了能阻止冒泡,还能阻止当前元素剩余的、同类型事件的执行。
react在进行element差异比较时,由于key的存在,可以准确地判断出该节点在新集合中是否存在,极大地提高了效率。但由于DOM节点移动操作开销昂贵,对于简单的node文本更改,不需要进行类似的diff过程,只需要更改dom.textContent即可,这时不加key的性能更高。
4.组件
组件的职责尽可能的保持单一,数据的获取和数据的渲染分开进行,这样在修改对应部分的时候不影响另一部分的使用,必要的时候可以使用高阶组件强制保持单一职责。
组件的封装秉承封装性,使组件的state结构只有自己知道。
5.数据状态管理
- React的state是在组件内部维护的数据,当某项state需要与其他组件共享时,可以通过props完成通信。从实践上来看,需要相对顶层的组件维护共享的state并提供修改此项state的方法,都要传给子孙组件。
- 使用Redux,在store中集中管理维护数据。任何需要访问并更新数据的组件都需要接入redux,借助容器组件完成订阅。
根据数据的持久度,不同数据的状态可分为:
- 快速变更型:某些原子级别的信息,显著特点是变更频率快。比如,文本输入框中的值随着用户输入在短时间内持续发生变化。这类数据显然更适合在React组件内维护。
- 中等持续型:在页面刷新前保持稳定。比如,异步请求接口通过ajax方式获取的数据,或者用户编辑提交的数据。这类数据较为通用,也许会被不同组件所使用,数据维护在redux store中,通过connect方法和组件进行连接。
- 长远稳定型:在页面多次刷新或多次访问期间都保持不变的数据。这类数据适合保存在服务器端数据库或浏览器的本地存储。
redux的限制体现在:
- 开发者要写很多“模式代码”,繁琐且重复。
- 需要使用objects或arrays描述状态。
- 需要使用plain objects 及actions来描述变化。
- 需要纯函数去处理变化。
- 要将应用中的状态抽象到store。
Mobx通过将对象和数组包装为可观察对象,隐藏了大部分样板代码,比redux更加简洁,也更加魔幻,像是进行了双向绑定。
6.函数式组件
面向生命周期编程以及class声明组件面向对象的行为不符合初衷,因此推出了函数式组件。组件只负责接收数据并渲染,清爽而直接。
react hook使开发者按业务逻辑拆分代码,而不是按生命周期,解决了函数式组件完全无状态的问题。
const Component = React.lazy(() => import())
export const AsyncComponent = props => {
<React.Suspense fallback={<Loading />}>
<Component {...props} />
</React.Suspense>
}
React.lazy带来了延迟加载的能力,封装动态import组件,import返回一个promise对象。
React.Suspense增加了组件异步(中断)渲染的能力,打破了之前同步渲染整个组件的格局。组件设置了fallback,当发现Component是一个promise类型,且没有被决议时,就启用fallback提供的组件,以便在等待网络返回结果时进行服务器端渲染。