前言

在三大前端框架中,Google开发的Angular.js出现最早但应用较少,国人开发的VUE在国内最火,今天的主角则是Facebook开发的React。

作为国内外最流行的前端框架,React最早被Facebook用于开发Instagram,在2013年5月开源。

React优点是,设计思想独特,性能出众,代码逻辑简单。一切以JS来实现,不使用模板,操作的对象是虚拟DOM。


虚拟DOM

虚拟DOM实际是React将DOM抽象成的JS对象,并可通过API来操作DOM。

也就是说,将DOM代码写到JS(script标签)中,而React负责将DOM代码进行渲染,并解析生成一个模拟DOM嵌套关系的虚拟DOM树对象。

再通过diff算法,在修改时逐层次的进行节点的比较,优先修改渲染虚拟DOM,对页面上真正发生变化的部分进行实际DOM操作,减少真实DOM的操作,实现页面元素的高效更新。

 

安装

直接通过npm安装。

npm i react
npm i react-dom
npm i babel-standalone

基础使用

首先需要引入react的js库

<!-- react核心文件 -->
<script src="react.js"></script>

<!-- 渲染页面中的DOM -->
<script src="react-dom.js"></script>

<!-- 将 ES6 代码转为 ES5 代码,这样就能在目前不支持 ES6 浏览器上执行 React 代码,并且Babel 内嵌了对 JSX 的支持,可以将JSX语法转换成JavaScript -->
<script src="babel.js"></script>

然后需要创建DOM根节点,根节点下的内容被React所管理,下面是一个简单的hello world示例。

<div id="app">test</div>

<script type="text/babel">
    let myDom = <h1>hello react</h1>
    ReactDOM.render(myDom, document.getElementById("app"))
</script>

 

JSX语法

上面代码中给myDom赋值的语法就是JSX,全称为JavaScript XML,是JavaScript的扩展语法。

优点是执行效率更高、类型安装,编译过程中就能及时发现错误、编写模板更加简单和快速。

需要注意的是以下几点:

 

1.必须严格按照W3C规范书写

例如`<input>`等自闭合标签必须写结束标签否则会报错。

let myDom = <input type="button" value=""></input>

 

2.JSX的注释方式与html和JS都不同

let myDom = <div>
    {/* 注释 */}
    <h1>myDom</h1>
</div>

 

3.不支持多行标签

如果有多行必须有父标签包裹,并且最好用括号包裹起来。

let myDom = (<div>
    <input type="button" value=""></input>
    <input type="button" value=""></input>
</div>)

 

4.赋变量、计算、调用函数等时同样需要用大括号包裹

let text = "hello react"
let count = 100
let user = {
    name: "zhangsan",
    age: 18
}
function aboutUser(userObj){
    return "name is " + userObj.name + ",age is " + userObj.age
}

let myDom1 = <div>{text}</div>
let myDom2 = <div>{count+1}</div>
let myDom3 = <h1>{aboutUser(user)}</h1>

 

5.使用 className来替代class

由于class是JavaScript的关键字,因此在JSX中需要使用className来设置类名。

let myDom = <div className="box"></div>

 

6.设置样式

let myDom = <h1 style={{color:"red"}}>testStyle</h1>

 

React元素与组件

React元素是构成React应用的最小部件,例如前面hello world实例中的<h1>hello react</h1>,与浏览器中的DOM元素不同,React元素是创建开销极小的对象,当React元素变化后,ReactDOM会自动去更新真实DOM。

React元素是不可变对象,一旦被创建就不能再更改它的子元素或者属性,更新它的唯一方式是创建一个全新的元素,并将其传入ReactDOM.render()。React元素代表了某个特定时刻的DOM元素状态。

而React组件(component)概念上类似于JavaScript函数,将React元素组成一个有特定功能、独立可复用的部件,能接受任意的入参(props),有自己的状态(state)和生命周期,并返回用于描述页面UI的JSX代码片段。

当应用的UI都是使用组件完成时,就是一个组件化的应用,能够实现页面局部功能的代码集合,简化页面复杂程度,提高运行效率,降低维护难度。

 

函数组件

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

var obj = {name:"zhangsan"};
let myDom = Welcome(obj);

// 或者写成以下形式
let myDom = <Welcome {...obj} />;

// React会将JSX所接收的属性(attributes)以及子组件(children)转换成单个对象(props)传递给组件
let myDom = <Welcome name="zhangsan" />;

 

class组件

class Welcome extends React.Component {
    render() {
        return <h1>hello,{this.props.name}</h1>;
    }
}

let myDom = <Welcome name="zhangsan" />

 

设置默认入参

可以通过defaultProps设置组件的默认参数,当传入参数缺失时自动调用默认参数。

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

Welcome.defaultProps = {name:"zhangsan"};

//默认参数不适用于Welcome()形式
let myDom = <Welcome />;

 

注意

1.组件名称必须以大写字母开头,小写字母开头的标签会被视为原生DOM标签。

2.传入的props参数为只读,不能进行修改,如果需要React组件随用户操作、网络响应或其他变化而动态更改输出内容,那么可以通过下面的状态(state)实现。

 

React组件的状态与生命周期

以下是一个每秒钟刷新一次的时钟组件,通过计时器每秒钟获取一次时间并重新渲染组件。

class Clock extends React.Component {
    render(){
        return (
            <div>
                <h1>Hello,react!</h1>
                <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
            </div>
        )
    }
}

function tick() {
    ReactDOM.render(
        <Clock date={new Date()} />,
        document.getElementById("app")
    )
}

setInterval(tick,1000)

这样显然是很蠢的实现方式,理想的情况下应该能够让时钟组件自我更新,下面代码就是利用state(状态)实现的时钟组件。

state与props类似,但state是私有的,并且完全受控于当前组件。

class Clock extends React.Component {
    // 构造函数,初始化state
    constructor(props) {
        super(props);
        this.state = {date:new Date()};
    }

    // 挂载(mount)函数,当组件第一次被渲染到DOM(render())之后执行,这里用来设置计时器。
    componentDidMount() {
        this.timerID = setInterval(
            ()=> this.tick(),1000
        );
    }

    // 卸载(unmount)函数,当DOM中组件被删除后执行,用于垃圾回收等。
    componentWillUnmount() {
        clearInterval(this.timerID)
    }

    tick(){
        // 更新state
        this.setState({
            date: new Date()
        })
    }

    render(){
        return (
            <div>
                <h1>Hello,react!</h1>
                <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
            </div>
        )
    }
}

ReactDOM.render(<Clock />,document.getElementById("app"))

其中,componentDidMount()(挂载函数)和componentWillUnmount()(卸载函数)就被称为生命周期函数。

 

注意

1.可以给state赋值的地方只有构造函数中。
2.state不能直接修改,应该使用setState()方法。

这样React就会知道state已经改变了,自动调用render()方法渲染更新后的组件。

3.state的更新可能是异步的。

原因是出于性能考虑,React可能会把多个setState()合并成一个调用。

//错误用法
this.setState({
    counter: this.state.counter + this.props.increment
})

//正确用法
this.setState((state,props) => {
    counter: state.counter + props.increment
})
4.数据是向下流动的。

state是私有的,不能直接被父组件或子组件访问,但可以将state作为props向下传递到子组件中。

<FormattedDate date={this.state.date} />

 

事件处理

创建事件

事件命名采用小驼峰式,并且需要传入函数作为事件处理函数。

// 传统HTML
<button onclick="handleClick()">testEvent</button>

//React
let myDom = <button onClick={handleClick}>testEvent</button>

 

阻止默认行为

传统HTML中可以通过返回false的方式阻止默认行为,而React中必须显式使用preventDefault。

//传统HTML
<a href="#" onclick="console.log('This link was clicked but refuse');return false">click me</a>
//React
function ActionLink() {
    function handleClick(e) {
        e.preventDefault();
        console.log('This link was clicked but refuse');
    }

    return (
        <a href="#" onClick={handleClick}>click me</a>
    )
}

 

class组件的事件处理函数

下面的代码是一个可以让用户切换开关状态的按钮。

class Toggle extends React.Component {
    constructor(props) {
        super(props);
        this.state = {isToggleOn: true};

        // 为了在回调中使用 `this`,这个绑定是必不可少的,否则回调中的this会是undefined
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        this.setState(state =&gt; ({
            isToggleOn: !state.isToggleOn
        }));
    }

    render() {
        return (
            &lt;button onClick={this.handleClick}&gt;
                {this.state.isToggleOn ? 'ON' : 'OFF'}
            &lt;/button&gt;
        );
    }
}

如果觉得bind麻烦,可以使用class fields正确的绑定回调函数。

class Toggle extends React.Component {
    constructor(props) {
        super(props);
        this.state = {isToggleOn: true};
    }

    handleClick = () => {
        this.setState(state => ({
            isToggleOn: !state.isToggleOn
        }));
    }

    render() {
        return (
            <button onClick={this.handleClick}>
                {this.state.isToggleOn ? 'ON' : 'OFF'}
            </button>
        );
    }
}

 

向事件处理函数中传递参数

//箭头函数方式,e为React的事件对象
let myDom = <button onClick={(e) => {handleClick(msg,e)}}>testEvent</button>

//Function.prototype.bind方式
let myDom = <button onClick={this.handleClick.bind(this,msg)}>testEvent</button>

因为害怕自己并非明珠而不敢刻苦琢磨,

又因为有几分相信自己是明珠,

而不能与瓦砾碌碌为伍。

《山月记》

——中岛敦