在整个前端体系发展的过程中,React 语法体系起到了至关重要的作用,Taro 也正是基于 React 语法来进行开发。本章我们逐一介绍一下 React 语法体系。
注:目前 Taro 所支持的 React 语法版本是以 React15 为基础的,所以本篇文章说的生命周期, API 等都是基于 React15 的版本。而想了解更多关于 React16 版本的变更情况,可到官网查阅。
JSX 语法
JSX 是 React 的核心组成部分,React 认为组件化才是正确的代码分离方式,它要比模板与组件逻辑分开的方式更好,所以就有了 JSX 语法。它把 HTML 模板直接嵌入到 JS 代码里面,这样就做到了模板和组件关联。JSX 允许在 JS 中直接使用 XML 标记的方式来声明界面,初次看起来,可能会有点糅杂的感觉,不太适应。但习惯了之后,也没有那么别扭,而且开发起来还蛮方便的。
举个 Taro 里的例子:
// 只看 render 函数,绝大多数情况下 render 函数里都会有 XML 标记
render () {
return (
<View className='index'>
<View className='title'>{this.state.title}</View>
<View className='content'>
{this.state.list.map(item => {
return (
<View className='item'>{item}</View>
)
})}
<Button className='add' onClick={this.add}>添加</Button>
</View>
</View>
)
}
可以见到,render 函数返回了一些用括号包住的 XML 结构的界面描述,这其实就是该组件的界面描述。里面的写法和 HTML 并没有多大的差别。不同的地方主要是可以在里面进行事件绑定,表达变量,实现简单 JS 逻辑等,即在 JS 里写 HTML。而变量、简单 JS 逻辑都是需要用 {} 包裹起来。另外 HTML 的 class 属性因为是 Javascript 的保留字,所以需要写成 className。
实际上,上述 JSX 代码,最终都会通过 JSX 编译器(现在主要都是 babel),转换为 JS 代码。例如:
// JSX 语法
const num = 1
const hello = <div className='test'>{num}</div>
// 编译后的 JS
const num = 1
const hello = React.createElement('div', {className:'test'}, num)
这里用的是 React 的例子(React.createElement 是 React 里的一个核心函数,用来生成虚拟 DOM ),Taro 中也会有类似的处理。简言之,JSX 其实是一种语法糖,最终会转化为生成虚拟 DOM 的 JS 代码。之所以有这种语法,我个人的理解是可以让你更自由,更开放,用 JS 的思维来处理界面与数据之间的关系,将界面抽象出来,不再受那些 HTML 的束缚。
然而,在 JSX 里使用 JS 是有限制的,只能使用一些表达式,不能定义变量,使用 if/else 等,你可以用提前定义变量;用三元表达式来达到同样的效果。
列表渲染,一般是用数组的 map 函数。正如上面的例子,把需要列表渲染的数据使用 map 函数,返回所需要的 JSX 代码。而在事件绑定上,使用 on + 事件名称,这点和我们理解的并无大异。
组件化,是 React 的另一大特点。顾名思义,组件化就是把整个页面分成大大小小的各个组件(component),然后像插入普通 HTML 标签一样,在页面中插入这个组件。组件之间可能互相关联,也可能互相独立,这取决于业务逻辑。这样做主要还是为了方便管理,把页面的维度降到组件的维度,维度越小,管理起来就越精确,同时,经过抽象之后,相同的组件还可以复用,提高了工作效率。
class Demo extends Component {
// ...
render () {
return <View className='test'>{this.state.num}</View>
}
}
这里需要注意,组件类的第一个字母必须大写,否则会报错,比如这里的 Demo 不能写成 demo。另外,组件类只能包含一个顶层标签,否则也会报错。
每个 React 组件都会有一个 render 函数,用于返回该组件的 JSX 片段,代表着该组件的界面结构。同时,也可以在 render 函数里返回另一个组件,即组件之间的相互嵌套,如下面所示,Demo 组件的 render 函数返回了一个 Nav 组件和一些 JSX 片段。
import Nav from './nav.js'
class Demo extends React.Component {
// ...
render () {
return (
<div className='test'>
<Nav />
<div className='bar'>{this.state.num}</div>
</div>
)
}
}
props 与 state 是 React 组件中最为重要的两个状态。
1、props
父组件传给子组件的数据,会挂载在子组件的 this.props 上,如:
// 子组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
// 父组件
class Demo extends React.Component {
render () {
return <Welcome name='aotu,taro!' />
}
}
// 最终页面上会渲染出 Hello, aotu,taro!
可以见到,Demo 组件在调用 Welcome 组件时,传入了 name=‘aotu,taro!’ 的 props,所以子组件里就能访问到 this.props.name 属性。比较重要的一点是,组件中如果收到了新的 props,就会重新执行一次 render 函数,也就是重新渲染一遍。
上述只是关于 props 的最基础的用法,除此之外,还有 this.props.children、高阶组件等比较高级的用法,在这里就不一一叙述了。
2、state
state 与 props 不同,是属于组件自己内部的数据状态,一般在 constructor 构造函数里初始化定义 state,例如:
class Welcome extends React.Component {
constructor(props) {
super(props);
this.state = {name: 'aotu,taro!'};
}
render() {
return <h1>Hello, {this.state.name}</h1>;
}
}
可以见到,在构造函数里初始化一个 state 对象,然后在 render 函数里渲染出来。
值得注意的是,当 state 需要变化时,是不允许随便更改的,需要调用 this.setState 来进行更改,否则视图没法进行更新。
// 错误
this.state.name = 'hey!hey!hey!'
// 正确
this.setState({name: 'hey!hey!hey!'})
这与 Vue 等一些可以在数据变更时自动响应视图变化的框架不同,React 的数据及视图变化是需要主动调用 setState 函数来触发的,具体原因与框架实现的原理有关,这里就不细说了。当调用 setState 函数时,最终组件会执行 render 函数,重新再渲染一遍。
所以一般来说,比较好的做法是只把跟组件内部视图有关联的数据,变量放在 state 里面,以此避免不必要的渲染。
组件的生命周期,指的是一个 React 组件从挂载,更新,销毁过程中会执行的生命钩子函数。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentWillMount() {}
componentDidMount() {}
componentWillUpdate(nextProps, nextState) {}
componentWillReceiveProps(nextProps) {}
componentDidUpdate(prevProps, prevState) {}
shouldComponentUpdate(nextProps, nextState) {}
componentWillUnmount() {}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
上述组件中基本把所有生命周期都列出来了,下面简单一一叙述下
constructor,顾名思义,组件的构造函数。一般会在这里进行 state 的初始化,事件的绑定等等
componentWillMount,是当组件在进行挂载操作前,执行的函数,一般紧跟着 constructor 函数后执行
componentDidMount,是当组件挂载在 dom 节点后执行。一般会在这里执行一些异步数据的拉取等动作
shouldComponentUpdate,返回 false 时,组件将不会进行更新,可用于渲染优化
componentWillReceiveProps,当组件收到新的 props 时会执行的函数,传入的参数就是 nextProps ,你可以在这里根据新的 props 来执行一些相关的操作,例如某些功能初始化等
componentWillUpdate,当组件在进行更新之前,会执行的函数
componentDidUpdate,当组件完成更新时,会执行的函数,传入两个参数是 prevProps 、prevState
componentWillUnmount,当组件准备销毁时执行。在这里一般可以执行一些回收的工作,例如 clearInterval(this.timer) 这种对定时器的回收操作
本文从 JSX 语法、React 组件、Props 与 State、生命周期这四个方面简单介绍了 React 语法体系的核心部分,整体而言都是一些比较基础的部分。而更详细,更多具体的开发技巧,随着小册的深入我们在后续涉及到的章节会继续介绍。