jsx 基础知识介绍
我们可以直接引⼊ react.js 和 react-dom.js 两个库,使⽤
react 时,我们可以使⽤ jsx 语法来书写我们的模版
<div className="foo">bar</div>
这是⼀种⾮常类似 DOM 的 xml 结构的语法,唯⼀有区别
的是,我们可以在 jsx 中使⽤ {} 来使⽤ js 表达式
<div className="foo">{something ? "something is
true" : "something is false"}</div>
这⾥的表达式当然也可以执⾏函数,数组 map 等等形
式,只需要这个式⼦是⼀个表达式即可,但这⾥要注意
千万不能直接在 jsx 中写执⾏诸如 if else 的形式,可以把
它改造成⼀个三⽬运算符
<div className="foo">{if(xx) {xx} else {xx}}
</div> // 错误
在 jsx 中我们需要保证渲染的内容必须是合法的 jsx 元
素,合法的 jsx 元素有:
- 普通的 DOM 标签,如 div/p/span 等等
- 声明的 react 组件,例如通过 class 或函数创建的 jsx 组件
- null(最终会渲染⼀个空元素)
- 字符串(最终会渲染⼀个 text 节点)
- 对于数字类型,最终就会渲染出来,所以有的时候通过布尔表达式判断的时候就会有问题
{false && (<p>this is false</p>)} // 不会渲染内容
{0 && (<p>this is false</p>)} // 会渲染 0
我们可以通过 React.isValidElement
来判断⼀个内容元素是不是⼀个合法的 react element。
同时需要注意的是,因为 class / for 这类的 html 属性是关键字,所以在 jsx 中我们想要使⽤,就必须使⽤className/htmlFor 的形式来定义
<label className="foo"
htmlFor="name">label</label>
事实上我们并不能直接在浏览器中使⽤ jsx 内容,我们需要搭配⼀些编译库将 jsx 语法进⾏编译。⽐较知名的就是 babel,搭配 babel-plugin-transform-react-jsx 插件,可以将 jsx 编译为 react 的内部⽅法。
例如这个例⼦,经过 babel 和配套插件就可以将这种形式进⾏编译:
<div>
<h3 className="h3">{something ? "something is true" : "something is false"}</h3>
</div>
最终结果即为:
React.createElement(
"div",
null,
React.createElement(
"h3",
{className: 'h3'},
something ? "something is true" :
"something is false"
)
);
React.createElement 主要分为三类参数,第⼀个是组件的名字,第⼆个参数是当前组件接受的属性,第三个之后的参数都是当前组件嵌套的⼦组件。
jsx 总结
- jsx 是⼀种语法糖,我们需要将他们编译为
React.createElement
的形式 - 写 jsx 需要注意类型必须合法,尤其是写布尔表达式的时候需要额外注意,尽量使⽤三⽬运算符来书写 jsx
- 需要注意 class 和 for 标签在书写时需要改为
className
和htmlFor
create-react-app cli 的使⽤
create-react-app 是 react 官⽅维护的⼀个 cli ⼯具,⾥⾯封装了 webpack babel 等基本的⼯程化⼯具,让我们能快速上⼿和使⽤。当然,我们也可以⾃⼰封装 webpack 配置来进⾏使⽤,封装⼀个可以编译 react 应⽤的脚⼿架⾮常简单,只需要配置所有 react ⽂件使⽤ babel + babel 转译 jsx 插件即
可,最终编译的 js 内容即可直接应⽤于⻚⾯中。
有时候我们可以⾃⼰封装⼀些符合公司内部前端架构的cli 应⽤。例如我们可以封装⼀个使⽤ ts 书写 react 应⽤的脚⼿架来为我们⾃⼰所⽤。
react中函数组件和 class 组件/受控组件和⾮受控组件
function Foo(props) { return (<div>{props.text
|| 'Foo'}</div>); }
class Bar extends React.Component {
render() {
return (
<div>{this.props.text || 'Bar'}</div>
);
}
}
这两种形式有何区别呢?我们简单对⽐⼀下:
- 加载的 props ⽅式不同,函数式定义组件从组件函数 的参数加载。class 形式的组件通过 this.props 获取传 ⼊的参数
- 函数式组件⽐较简单,内部⽆法维护状态。class 形式 内部可以通过 this.state 和 this.setState ⽅法更新内部state 和更新内部 state,同时更新 render ⾥⾯的函数 渲染的结果。
- class 组件内部可以定义更多的⽅法在实例上,但是函 数式组件⽆法定义。
事实上,函数式组件和 class 组件之间的区别也仅停留在部分组件确实不需要维护内部状态。class 组件定义稍微复杂⼀些,但是内部可以维护更多的⽅法和状态。
react组件⽣命周期
- 加载阶段(Mounting):在组件初始化时执行,有一个显著的特点:创建阶段生命周期函数在组件的一辈子中只执行一次;
constructor()
加载的时候调用一次,可以初始化state static getDerivedStateFromProps(props, state)组件每次被Rerender的时候,包括在组件构建之后(虚拟dom之后,实际dom挂载之前),每次获取新props/state之后;每次接收新的props之后都会返回一个对象作为新的state,返回null则说明不需要更新state
render()
react最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行
componentDidMount()
组件渲染之后调用,只调用一次
- 更新阶段(Updating):属性和状态改变时执行,根据组件的state和props的改变,有选择性的触发0次或多次;
render()
react最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行
getSnapshotBeforeUpdate(prevProps, prevState)
触发时间: update发生的时候,在render之后,在组件dom渲染之前;返回一个值,作为componentDidUpdate的第三个参数;配合componentDidUpdate, 可以覆盖componentWillUpdate的所有用法
componentDidUpdate()
组件加载时不调用,组件更新完成后调用
- 卸载阶段(Unmounting):在组件对象销毁时执行,一辈子只执行一次;
componentWillUnmount()
组件渲染之后调用,只调用一次
react常⻅错误和性能问题
1. 异步过程使⽤单例的 event 对象
全局单例的 event 对象,所以在异步对象中使⽤ react 事件时需要额外注意。异步操作最好将对象内部需要的值先进⾏拷⻉赋值。
handleClick(e) {
setTimeout(function() {
console.log('button1 click',
e.currentTarget.innerText); // 错误
}, 1000);
console.log('button1 click',
e.currentTarget.innerText);
}
组件⽣命周期,它描述了整个组件在创建、实例化、销毁过程中不同过程中执⾏的⽅法。我们需要特别注意,不要在 render 中定义单独引⽤的内容。也就是不要在render 中使⽤箭头函数,否则很容易运⾏时造成⼦组件的重新渲染。
为了保证这种引⽤的相等,我们都会使⽤ immutable 的不可变数据,来保证组件间传递的数据引⽤相等。
2.性能优化⽅式
- 使⽤ react dev tools ,检测组件是否出现不必要的重新渲染。
- why-did-you-render
why did you render 是⼀个能检测你的⻚⾯中的元素是否出现了不必要的重渲染。我们可以将它应⽤于我们的项⽬中,来检测是否有⽆
意义的渲染的情况。 - class 中提前声明箭头函数,保证 render 执⾏过程中 的函数不会因为引⽤问题导致重新渲染。
3.介绍 immutable 库 immutable-js 和 immer
为了配合 shouldComponentUpdate 来进⾏性能优化,⼤部分时候我们需要复杂的层级判断,这⾥我们介绍两个配合 react 最⼩更新的 immutable 库 immutable-js 和immer。
immutable-js 是 facebook 的⼯程师在 2014 年推出的,immer 则是 mobx 作者 2018 年推出的。他们的推出其实是为了实现不可变数据,但实际上这种做法更多的是为了优化我们的 react 应⽤⽽做的。
我们可以简单看⼀下 immutable-js 的⽤法,着重讲⼀下immer 的原理,便于⼤家更好的理解数据传递过程中的不可变性。