我的React之路--入门

React的学习之路还要继续走下去,最近一边在做未完成的项目一边学习React,项目是vue写的,后面还需要有一个后台管理系统计划使用react完成,寒假说长也不长,要抓紧时间了。

有人爱有人恨的语法糖–jsx

jsx简介

很多人不喜欢React,很大程度上是因为不喜欢jsx,那么jsx到底是什么呢?首先还是不要忘了React的基本哲学–一切都是js,包括文档结构。所以曾经每天都会见到的html在react的世界里全都不存在,react通过一系列叫做react元素的对象来构建虚拟DOM结构,最原始的创建react元素的方式是这样的:

1
const root = React.createElement('div', { className: 'main' }, '我是一个div');

最终它将返回一个大概这样子的对象(有所简化过,不代表在 React 源码中是这样):

1
2
3
4
5
6
7
const root = {
type: 'div',
props: {
className: 'main',
children: '我是一个div'
}
};

这样一个个创建节点其实是很麻烦的,想想看,如果每个整个虚拟DOM的内容都要通过React.createElement来创建,代码量会很多,而且我们根本无法直观地看出树形结构,无论开发还是维护性都及其不友好。为了解决这一问题,一种新型的,类似xml结构的语法扩展就诞生了,就是jsx。

上面的代码结构改成jsx的书写方式就是这样的:

1
2
3
4
5
const root =(
<div className="main">
我是一个div
</div>
);

这个结构就很熟悉了,不过要记住,他不是html模板,它就是js,最终在执行之前会被完全转义成为纯js代码,所以使用jsx是不存在任何性能问题的。

jsx语法

jsx的标准语法结构和xml完全类似,特别的,jsx中的html属性要写成小驼峰命名的形式,比如onclick就要写成onClick。另一点需要注意的问题就是,由于class是js中的保留字,所以要用className来代替。

jsx使用jsx时候要时刻记住它是js表达式,所以它可以像普通的js表达式一样,赋值传参返回都可以。而在jsx内部如果想使用表达式,就需要放在{}里面。这就是jsx语法,非常简单,也不需要记忆特殊的指令,一切都可以和处理js一样来处理,下面来看一个小例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
const item = this.newsList.map((news, index) => (
<li key={index}>
<span>{news.title}</span>
<span>{news.desc}</span>
{news.image ? <img src="{news.image}"/> : null}
</li>
)
);
const list = (
<ul>
{item}
</ul>
);

这是一个在react开发中特别常见的使用场景,我们得到了一个数组的数据,需要以列表的形式渲染出来。在react中不需要使用任何迭代判断的相关指令语法,只要会写js的都能看懂上面的逻辑:通过数组的map方法迭代数组的内容,在回调函数中处理数据,渲染成想要的样式,就得到了一条一条列表项,插到列表里面就完成数据渲染了。我们可以发现在不论是迭代方法还是三元表达式等等,只要是js语法就可以随意地写到里面,自由度非常高。

可复用的基本单位–组件

为什么要使用组件

了解了jsx,下一个重要概念就是组件了。组件不是react特有的,组件化开发有很多好处,组件符合高内聚低耦合的要求,每一个组件是封装了视图和逻辑的一个相对独立的个体,而整个页面是由多个组件构成的,每个组件可以多次复用。

组件可以理解为类似于函数调用一样,定义好的组件是一个抽象的视图,而我们通过传入相关的“参数”来使它展示出我们想要的样子,组件就是我们复用各种独立部件的基本单位。

React中的组件

定义一个组件最简单的方式是使用JavaScript函数:

1
2
3
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

这是一个最简单的函数定义组件,整个函数调用结果实际上就是返回一个<h1>标签,不过特别的是,标签的内容不是确定的,它是由我们传入的参数来决定的。这就是组件开发,在React中,最常用的不是函数声明组件,而是向下面一样:

1
2
3
4
5
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

此处使用了ES6的类和继承,创建了一个继承自Component的类。这个组件和上一个组件效果是完全相同的,下面来重点分析React组件中几个重要的概念。

核心数据状态–state

什么是state?状态,在react组件中,state是指一个组件UI呈现的最小状态集。在react中,视图层的更新是通过处理状态的变化来实现的,而state就是对这一系列状态的定义。react的数据是单向流动的,数据只能从模型层流向视图层,对应到具体的实现,我们对state所做的一系列处理会自动的反映到视图上,我们想要更新视图,只更新状态即可。说的可能比较抽象,看一个具体例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ClickMe extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
clicked() {
this.setState({
count: this.state.count + 1
});
}
render() {
return (
<div onClick={() => this.clicked()}>
点我{this.state.count}次
</div>
);
}
}

这个组件的效果就是点击文字,会显示你的点击次数,效果很简单就不截图了,关于组件生命周期和点击事件绑定后面再看,这里重点来看state的变化。在构造函数里面初始化state的数据,把state数据放在页面上,点击时候调用setState方法改变state中的数据。

关于state有几点需要注意:

  1. state不能直接修改,直接修改state的值是不会更新视图的,正确的更新方式是使用setState来改变state的值。
  2. 不是所有的变量都要放到state中,state里面的变量一方面是要来描述组件自身状态,不需要反映到视图上的内容不是state。
  3. state是最小状态集,取自父组件的状态信息不是自身状态,不能放在state中。从外部传入的东西要放在props中。

外部传递属性–props

props是组件的另一个非常重要的概念,props指的是从外部传入的属性。props是React中父组件向子组件通信的方式,下面是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
class Child extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
{this.props.data}
</div>
);
}
}

使用组件

1
<Child data="我是显示的数据"></Child>

我们定义组件时候在构造函数中可以接收到props参数,并且要使用super传到Component的构造方法中。在整个组件的类中就可以使用成员变量props了。而props的内容,是父元素在调用子元素时候以属性的形式传入的。整个props控制的就是从父元素到子元素的事件流,这样我们在使用组件的时候就可以像函数调用一样使用组件,像传入参数一样传入props。

使用props时候要注意一点,props传递数据是单向的,数据只能从父组件传递到子组件,需要其它方向的数据传递就需要使用其他方式了。

组件从创建到销毁–生命周期

生命周期这个概念在很多开发中都会接触,react也是如此,一个react组件从创建运行到销毁需要经历很多阶段,系统也为我们提供了对应阶段的hook方法(hook方法翻译为钩子方法,指的是当组件运行到对应的阶段时候就会自动执行写在这些方法里面的逻辑),我从网上找到了一副描述比较清晰的图片(侵删):

下面来逐一介绍这些生命周期方法以及它们说发挥的作用

  1. getDefaultProps和getInitialState,如果使用ES6的类继承方式定义组件是看不到这两个方法的,它们的任务是组件加载前先获取默认props和初始化state,在ES6的语法中我们可以在constructor中对其进行定义,注意constructor第一句必须要使用super(props),否则会报错。
  2. componentWillMount,在组件渲染之前调用,整个生命周期只会调用一次,子组件的该方法会在父组件调用之后被调用,如果在该方法内设置状态,react会在状态设置好之后才执行渲染,常用在该方法里发送网络请求获取数据。
  3. render(),组件渲染方法,此方法返回组件最终被渲染的状态,它的作用就是渲染组件,此阶段不能修改state。从图上可以看出,除了首次渲染要调用,此方法在组件发生更新时候也会被调用,它是组件最核心的方法。
  4. componentDidMount,在逐渐被渲染之后被调用,仅调用一次,子组件的此方法会在父组件的此方法之前调用,此方法结束后组件进入运行状态。
  5. componentWillReceiveProps(nextProps),组件运行阶段,当组件接收到新的props时被调用,这个函数接收一个object参数(新的props),父组件发生render的时候子组件就会调用,组件首次渲染不会触发。
  6. shouldComponentUpdate(nextProps, nextState),组件运行阶段,接收到新的state或props时被调用,此方法默认返回true,可以通过控制该方法返回false来阻止组件重新渲染。
  7. componentWillUpdate,组件运行阶段,当准备重新渲染组件前调用,做一些渲染前准备工作,组件首次渲染不会触发。
  8. componentDidUpdate,组件运行状态,组件重新渲染之后调用,组件首次渲染不会触发。
  9. componentWillUnmount,在组件被卸载前调用,做一些结束前的清理工作。

以上是react生命周期的相关内容,到此,react组件的基本概念就介绍的差不多了。

行为与交互–事件绑定

在react中绑定事件需要注意一个问题,如果是使用ES6的class方式定义的组件中事件处理函数的this默认是不会绑定的,我们需要手动绑定this指向。来看下面一个错误的例子:

1
2
3
4
5
6
7
8
9
10
11
12
class EventTest extends React.Component {
clicked() {
console.log('clicked');
}
render() {
return (
<div onClick={this.clicked}>
点我
</div>
);
}
}

点击点我,确实能够正常打印出clicked,看起来好像没有问题,但是,如果试着打印一下this,就会发现结果是undefined。

这样写this没办法绑定,自然也就没办法使用各种成员变量和方法,也不能调用内置方法了,显然不是我们预期的,所以我们需要手动来绑定this指向,方法也很简单:

1
2
3
4
5
6
7
8
9
10
11
12
class EventTest extends React.Component {
clicked() {
console.log('clicked');
}
render() {
return (
<div onClick={this.clicked.bind(this)}>
点我
</div>
);
}
}

只要增加bind(this)就能实现预期效果了,这也是一种常用的绑定this方式。除此之外还可以采用箭头函数来自动绑定this,下面的做法也是完全可以的:

1
2
3
4
5
6
7
8
9
10
11
12
class EventTest extends React.Component {
clicked() {
console.log('clicked');
}
render() {
return (
<div onClick={()=>this.clicked()}>
点我
</div>
);
}
}

把clicked作为箭头函数返回的函数来使用,利用箭头函数内部自动绑定this的特性也可以实现this绑定。另外,还有一种写法:

1
2
3
4
5
6
7
8
9
10
11
12
class EventTest extends React.Component {
clicked = () => {
console.log('clicked');
}
render() {
return (
<div onClick={this.clicked}>
点我
</div>
);
}
}

这种方法是新的ES标准中的实验性语法,由于有babel转译也是可以使用的,官网上面也提到了这种写法,不过由于新的标准还未成熟,所以用的人也不多。


了解了这些,react算是入了门了,接下来深入学习的路还长,虚拟DOM的原理,diff算法,css-in-js,工程化下的react项目结构,react-router,redux,还有以后要学习的react native,后面随着学习慢慢总结。

-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!