React介绍
谷歌大法,一搜一大把
React环境安装
安装react
,react-dom
模块:
1 | cnpm install react react-dom --save |
因为react中使用了JSX语法,所以需要babel
进行转换:
1 | cnpm install babel-preset-react babel-core --save-dev |
如果项目使用ES6语法,还需要一个ES6转ES5的preset
:
react官方已极力推荐使用ES6语法编写应用,官方的demo都是基于ES6的。
1 | cnpm install babel-preset-es2015 --save-dev |
React团队提供了一个
create-react-app
的工具,只需在命令行输入create-react-app <app-name>
,工具会帮你安装react
,react-dom
,react-script
并生成项目的基本框架。
webpack配置
在安装完成后我们首先编写一段react语法的代码
main.js:
1 | import React from 'react' |
index.html
1 |
|
全局安装webapck
:
cnpm install webpack -g
在项目根目录安装webpack
:
cnpm install webpack --save-dev
然后在配置webpack.config.js
:
1 | var webpack = require('webpack'); |
然后执行webpack
打包就可以了
如果要热更新的话可以再安装:
cnpm install webpack-dev-server --save
然后执行:webpack-dev-server --progress --inline
来启动热更新的服务器
当然也可以将这个命令写到package.json
的scripts
里,然后通过npm run dev
来启动
1 | "scripts": { |
或者一次性安装所有的:
cnpm install babel-core babel-loader babel-preset-es2015 babel-preset-react react react-dom webpack webpack-dev-server --save
React组件
组件化开发是react开发中最重要的功能,可以简单的认为react就是开发组件的.
组件系统react的重要概念,因为它是一种抽象,允许我们使用小型、自包含和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树:
定义react组件
编写一个Header
组件:
1 | import React from 'react' |
render方法是React组件必要的方法。render方法中使用JSX语法定义DOM结构。
如果组件比较简单,还可以使用函数式声明的组件:
1
2
3
4
5 function Header() {
return <header>
<h1>我是头部</h1>
</header>
}具体使用详见官方文档:https://reactjs.org/docs/components-and-props.html#functional-and-class-components
在main.js
中引用上面定义的组件:
1 | import React from 'react' |
render方法中只允许有一个顶层元素返回,不过React提供了一个
React.Fragment
可以让你嵌套多个子元素
1
2
3
4
5
6
7
8 render() {
return (
<React.Fragment>
Some text.
<h2>A heading</h2>
</React.Fragment>
);
}
JSX
JSX=JS+XHTML,其中XHTML要求HTML格式符合XML标准,不能像写HTML一样随意:标签闭合,空标签<tag/>
。
JSX基本的解析规则:遇到 HTML 标签(以 <
开头),就用 HTML 规则解析;遇到代码块(以 {
开头),就用 JavaScript 规则解析。上面代码的运行结果如下。
其实JSX本质上会创建虚拟DOM(virtual-dom)对象。
比如上面的Header定义会被转换成:
1 | export default class Header extends React.Component{ |
React.createClass就是用来创建React组件的。
React.createClass是ES5创建组件的方式,新版本的React推荐使用继承React.Component的方式创建组件。如果不想使用ES6,需要添加
create-react-class
模块依赖,具体可以参考官方文档:React Without ES6
React.createElement就是用来创建虚拟DOM对象的。
注意:上面的header元素不通过class而使用className来引用CSS类,不仅仅因为class与ES6的关键字冲突;最主要的原因React将标签中的所有属性最终会作为一个对象传给
React.createElement
方法,JS中通过className
(字符形式)或classList
(数组形式)属性来操作class,显然用className更合适。
虚拟DOM
参考React官网的文档:https://reactjs.org/docs/faq-internals.html#what-is-the-virtual-dom
Web App性能问题主要出在 DOM 对象的操作上,比如读写,创建,插入等等。之所以原生 DOM 性能低,是因为 DOM 的规范迫使浏览器在实现的时候为每一个 DOM 元素添加了非常多的属性,然而这其中很多我们都用不到。
具体到 React 主要是做了两点:
其一是 VirtualDOM,一个很简化的虚拟文档对象模型系统,你可以操作类似 DOM 的对象,但是非常轻量化(这就是为啥 JSX 看起来是在 JS 代码里写 XML 的缘故,这是一层语法糖,方便开发者编写模板,但实际上还是 JS 对象,而不是真实的 DOM 对象);
其二则是当数据变化的时候,不直接去修改 DOM(因为变化的只是个别属性,但是修改 DOM 往往却要替换一整个 DOM 对象),而是先用dom diff算法(简单的理解成git diff
这样的命令)比较前后的差异,最后只把变化的部分一次性应用到真实的 DOM 树上去。
可以联想到游戏开发中经常提到的“双缓冲”机制,两者有异曲同工之妙
使用组件对象的props
访问标签属性
1 | class Header extends React.Component{ |
this.props.children
访问标签子元素
props中还有一个特殊的属性children
用于访问标签中的子元素。
1 | class List extends React.Component{ |
最终会被渲染成:
1 | <ol> |
因为标签子元素个数不确定:没有子元素,
children=undefined
;一个子元素,值为object类型;多个子元素,就是array类型。为了方便操作React在
React.Children
中提供了一套工具方法,方便操作子元素。
使用defaultProps
定义默认的props
如果标签中没有指定属性值,this.props
会取出undefined
值,可以在组件的defaultProps
中定义默认的属性值。
1 | class Header extends React.Component{ |
如果不使用ES6,在调用createReactClass时,需要在对象中定义一个
getDefaultProps
方法。详见:https://reactjs.org/docs/react-without-es6.html#declaring-default-props
对props中的属性进行类型检查
参考:https://reactjs.org/docs/typechecking-with-proptypes.html
为了限制组件中属性的类型,需要React.PropTypes
进行类型检查。
从React v15.5开始,
React.PropTypes
被移进了prop-types
库。
1 | import PropTypes from 'prop-types'; |
获取真实的DOM节点
前面说到React的render方法创建的是虚拟DOM对象,它并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,当虚拟DOM更新到浏览器的DOM后,我们要怎么操作真实的DOM节点呢?这时就要用到 ref
属性。
String refs
先看老版本API中的ref
怎么使用:
1 | class MyComponent extends React.Component{ |
传统的方式要将在元素中定义ref
属性,相当于给元素定义了一个id,然后组件中可以在this.refs
属性根据ref
指定的id获取标签元素。
ref callback
新版本的不再使用字符引用的方式,这里有关于两者的讨论。
1 | class MyComponent extends React.Component{ |
setState方法改变组件状态this.state
UI组件避免不了与用户的交互,有交互就避免不了对组件的修改。React将组件看成一个状态机,一开始有一个初始状态,一旦有状态的改变,就会触发重新渲染。
1 | class LikeButton extends React.Component { |
注意:
- 不要通过
this.state.comment = 'Hello';
这种方式直接修改控件状态,因为这样不会触发控件重新渲染。而应该使用setState
修改状态。this.state = XXX
这种赋值操作只能在构造函数中初始化时使用。
在ES5中使用React时,需要在createReactClass方法中的提供**
getInitialState
**方法返回初始状态。详见官方文档:https://reactjs.org/docs/react-without-es6.html#setting-the-initial-state
根据前一个状态计算后续状态
因为setState是异步更新状态,我们不应该直接依赖当前的state值计算下一个状态:
1 | // Wrong |
组件生命周期
参考:https://reactjs.org/docs/state-and-lifecycle.html
https://reactjs.org/docs/react-component.html#the-component-lifecycle
每个组件都有几个生命周期方法,可以重写这几个方法,React在特定的时候会回调这些方法。
这些生命周期方法有些特征:
前缀为will的方法表示某些事件即将发生;前缀为did的方法表示某些事件已经发生
当组件实例被创建,再到组件被插入到DOM树中,这个过程会触发组件的以下方法:
当props
或state
被修改导致组件更新,组件渲染的时候会回调下面这些方法:
当组件从DOM树中移除的时候,会触发componentWillUnmount方法:
在组件渲染、生命周期回调或者构造子组件的过程中出现异常,会触发组件的componentDidCatch方法:
方法的具体介绍可以点击相应链接看官方文档的介绍
一个简单的时钟组件例子
component/TimerClock.jsx
1 | import React from 'react' |
index.jsx
1 | import React from 'react' |
index.html
1 |
|
webpack.config.js
1 | var webpack = require('webpack'); |
package.json
1 | { |
参考: