简介

React是Facebook开发并开源的前端框架.
当时他们的团队在市面上没有找到合适的MVC框架,就自己写了一个Js框架,用来假设大名鼎鼎的instagram(图片分享社交网络)。2013年React开源。

React解决的是前端MVC框架中的View视图层的问题。

Virtual DOM

DOM(文档对象模型Document Object Model)

image.png

将网页内所有内容映射到一棵树型结构的层级对象模型上,浏览器提供对DOM的支持,用户可以是用脚本调用
DOM API来动态的修改DOM结点,从而达到修改网页的目的,这种修改是浏览器中完成,浏览器会根据DOM的改
变重绘改变的DOM结点部分。

修改DOM重新渲染代价太高,前端框架为了提高效率,尽量减少DOM的重绘,提出了Virtual DOM.所有的修改
都是现在Virtual DOM上完成的,通过比较算法,找出浏览器DOM之问的差异,使用这个差异操作DOM,浏览器
只需要渲染这部分变化就行了。

React实现了DOM Diff算法可以高效比对VirtualDOM和DOM的差异。

支持JSX语法

JSX是一种JavaScript和XML混写的语法,是Javascript的扩展。

React.render(
<div>
    <div>
    <div>content</div>
    </div>
</div>,
document.getElementById('example')
);

测试程序

替换 /src/index.js 为下面的代码

import React from 'react';
import ReactDom from 'react-dom'

class Root extends React.Component
    render() {
        return <div>Hello I'm Gloo</div>;
    }
}
ReactDom.render(<Root/>, document.getElementById('root')) ;

保存文件后,会自动编译,并重新装载刷新浏览器端页面。

image.png

程序解释

import React from 'react';导入react模块
import ReactDoM from 'react-dom';导入react的DOM模块

class Root extends React.component组件类定义,从React.Component类上继承。这个类生成JSXElement对象即React元素。

render()渲染函数。返回组件中渲染的内容。注意,只能返回唯一一个顶级元素回去。

ReactDom.render(<Root/>, document.getElementById("root");第一个参数是JSXElement对象,第二个是DOM的Element元素。将React元素添加到DOM的Element元素中并渲染。

还可以使用React.create Element创建react元素 ,第一参数是React组件或者一个HTML的标签名称(例如div、span)

return React.createElement ('div', null, "Hello I'm Gloo.");
ReactDom.render(React.createElement (Root), document.getElementById('root'));

改写后代码为

import React from 'react';
import ReactDom from 'react-dom';

class Root extends React. Component {
    render() { 
        //return ‹div›Hello magedu</div>; 
        return React.createElement('div', null, "Hello I'm Gloo.");
    }
}

//ReactDom. render (‹Root/>, document.getElementById( 'root')) ;
ReactDom.render (React. createElement (Root), document.getElementById( 'root'));

很明显SX更简洁易懂,推荐使用)SX语法.
增加一个子元素

import React from 'react';
import ReactDom from 'react-dom';

class SubEle extends React.Component {
    render() {
        return <div>Sub content</div>;
    }
}

class Root extends React.Component {
    render() {
        return (
        <div>
            <h2>Hello I'm Gloo</h2><br />
            <SubEle />
        </div>
        );
    }
}

ReactDom.render(<Root />, document.getElementById('root'));

注意:
1、React组件的render两数return,只能是一个顶级元素
2、JSx语法是XML,要求所有元素心须闭合 ,注意sbr /,不能写成

JSX规范

  • 标签中首字母小写就是html标记,首字母大写就是组件
  • 要求严格的HTML标记,要求所有标签都必须闭合。br也应该写成<br />,/前留一个空格。
  • 单行省略小括号,多行请使用小括号
  • 元素有嵌套,建议多行,注意缩进
  • JSX表达式:使用{}括起来,如果大括号内使用了引号,会当做字符串处理 ,例如<div>{'2>1?true:false'}</div>,里面的表达式成了字符串了

组件状态state **

每一个React组件都有一个状态变量state,它是一个javascript对象,可以为它定义属性来保存值。如果状态变化了,会触发U的重新渲染。使用setState0方法可以修改state值。
注意:state是组件自己内部使用的,是组件私有的属性.

依然修改/src/index.js

import React from 'react';
import ReactDom from 'react-dom';

class Root extends React. Component {
    state = {
        p1: 'gloo',
        p2: '.com'
    };

    render() {
        this.state.p1 = www.gloo';  // 可以更新
        // this.setstate({ p1:'www.gloo' });// 不可以对还在更新中的state使用setstate
        // Warning: setstate(...): Cannot update during an existing state transition (such as within render).
        // Render methods should be a pure function of props and state.
        return(
        <div>
        <div>Welcome to {this.state.p1}{this.state.p2}</div›<br />
        </div>
        );
    }
}
ReactDom.render(<Root />, document.getElementById ('root'));

如果将this.state.p1 =' www.magedu'改为this.setstate(fp1:' www.magedu'});就会出警告
可以使用延时函数setTimeout( ()=> this.setstate({ p1: 'www.magedu'}, 5000);即可

复杂状态例子

先看一个网页

<html>
<head>
    <script type="text/javascript">
    function getEventTrigger (event) {
        x = event.target; //
        alert("触发的元素的id是:"+x.id);
    }
    </script>
</head›
<body>
    <div id="t1" onmousedown="getEventTrigger(event)">
    点击这句话,会触发一个事件,并弹出一个警示框
    </div>
</body>
</html>

div的id是t1,鼠标按下事件捆鄉了一个函数,只要鼠标按下就会触发调用getEventTrigger函数,浏览器会送给它一个参数event。 event是事件对象,当事件触发时,event包含触发这个事件的对象。

HTML DOM的Javascript事件

属性此事件发生在何时
onabort图像的加载被中断
onblur元素失去焦点
onchange域的内容被改变
onclick当用户点击某个对象时调用的事件句柄
ondblclick当用户双击某个对象时调用的事件句柄
onerror在加载文档或图像时发生错误
onfocus元素获得焦点
onkeydown某个键盘按键被按下
onkeypress某个键盘按键被按下并松开
onkeyup某个键盘按键被松开
onload一张页面或一幅图像完成加载
onmousedown鼠标按钮被按下
onmousemove鼠标被移动
onmouseout鼠标从某元素移开
onmouseover鼠标移动某元素之上
onmouseup鼠标按键被松开
onreset重置按钮被点击
onresize窗口或框架被重新调整大小
onselect文本被选中
onsubmit确认按钮被点击
onunload用户退出页面

使用React实现上面的传统的HTML

import React from 'react';
import ReactDom from 'react-dom';
class Toggle extends React. Component {
    state = { flag: true }; // 类中定义state

    handleClick(event){
        console.log(event.target.id);
        console.log(event.target === this);
        console.log(this);
        console.log(this.state);
        this.setstate(( flag: !this.state.flag });
    }

    render() {/* 注意一定要鄉定this onclick写成小驼峰*/
        return <div id="t1" onClick={this.handleClick.bind(this)}>
        点击这句话,会触发一个事件。{this.state.flag.tostring(}
        </div>;
    }
}

class Root extends React.Component {
    state = { pl:'www.gloo', p2:'.com'};  // 构造函数中定义state
    render() {
        // this.state.p1 = 'python.gloo';  //可以修改属性值
        // this.setstate ({p1:'python.gloo'});//不可以对还在更新中的state使用setstate
        // Warning: setstate(...); Cannot update during an existing state transition (such as within render).
        setTimeout (() => this.setState ({ p1: 'python.magedu' }), 5000);
        // setInterval(() =› this.setState({ p1: 'python.magedu' }), 5000);
        return (
        <div>
        <div>Welcome to {this.state.p1}{this.state.p2}</div›
        <br />
        <Toggle />
        </div>);
    }
}
 
ReactDom.render(<Root />, document.getElementById ('root')) ;

分析
Toggle类
它有自己的state属性。
当render完成后,网页上有
一个div标签,div标签对象捆绑了-
个click事件的处理函数,div标签内有文本内容。
如果通过点击左键,就触发了click方法关联的handleClick函数,在这个函数里将状态值改变。
状态值state的改变将引发render重绘,
如果组件自己的state变了,只会触发自己的render方法重绘。

注意:
{this.handleClick.bind(this)},不能外加引号
this.handleClick.bind(this)一定要绑定this,否则当触发捆绑的函数时 ,this是函数执行的上下文决定的,this已
经不是触发事件的对象了。
console.log(event.target.id),取回的产生事件的对象的id,但是这不是我们封装的组件对象。所以,
console.log(event.target===this)是false。所以这里一定要用this,而这个this是通过綁定来的。
React中的事件

  • 使用小驼峰命名
  • 使用JSX表达式,表达式中指定事件处理函数
  • 不能使用return false,如果要阻止事件默认行为,使用event.preventDefault()

属性props **

把React组件当做标签使用,可以为其增加属性,如下
<Toggle name="school" parent={this} />
为上面的Toggle元素增加属性:

  1. name = "school",这个属性会作为一个单一的对象传递给组件,加入到组件的props属性中
  2. parent = {this},注意这个this是在Root元素中,指的是Root组件本身
  3. 在Root中为使用SX语法为Toggle增加子元素,这些子元素也会被加入Toggle组件的props.children中
import React from 'react';
import ReactDom from 'react-dom';

class Toggle extends React. Component {
    state = { flag: true }; // 类中定义state

    handleClick(event){
        console.log(event. target.id);
        console.log(event. target === this);
        console.log(this);
        console.log(this.state);
        this.setstate({ flag: !this.state.flag });
    }

    render() {/* 注意一定要绑定this onclick写成小驼峰*/
        return <div id="t1" onclick={this .handleclick.bind (this)}>
        点击这句话,会触发一个事件。{this.state.flag. tostring()j<br />
        显示props<br/>
        {this.props.name}:{this.props.parent.state.p1 + this.props.parent.state.p2}<br />
        {this.props.children}
        </div>;
    }
}

class Root extends React.Component {
    state = { p1: 'www.gloo', p2: '.com' };  //构造函数中定义state
    render() {
        // this.state.p1 ='python.magedu';  //可以修改属性值
        // this.setstate({p1:'python.magedu'});  //不可以对还在更新中的state使用setstate
        // Warning: setstate(...): Cannot update during an existing state transition (such as within render).
        setTimeout (() => this.setState({ p1: 'python.magedu' }), 5000);
        // setInterval(0) => this.setState({ p1: 'python.magedu' }), 5000);
        return (
        <div>
        <div>Welcome to {this.state.p1}{this.state.p2}</div›
        <br />
        <Toggle name=" schoo1" parent={this}>{/*自定义2个属性通过props传给Toggle组件对象*/}
        <hr />/*props. children*/
        <span>我是Toggle元素的子元素</span>{/ * 子元素通过props.children方问*个
        </Toggle>
        </div>);
    }
}

ReactDom.reander(<Root />, document.getElementById('root'));

尝试修改props中的属性值,会抛出TypeError: Cannot assign to read only property 'name' of object '# <obiects>异常。

应该说,state是私有private属性 ,组件外无法直接访问。可以修改state,但是建议使用setstate方法。
props是公有public属性,组件外也可以访问,但只读。

构造器constructor

使用ES6的构造器,要提供一个参数props,并把这个参数使用super传递给父类

import React from 'react';
import ReactDom from 'react-dom';

class Toggle extends React. Component {
    constructor (props) {
        super(props);  // 一定要调用super父类构造器,否则报错
        this.state = { flag: true };  // 类中定义state
    }

    handleClick(event){
        console.log(event.target.id);
        console.log(event. target === this);
        console.log(this);
        console.log(this.state);
        this.setState({ flag: !this.state.flag });
    }

    render() {  //* 注意一定要绑定this onclick写成小驼峰 *
        return <div id="t1" onClick={this.handleClick.bind(this)}>
        点击这句话,会触发一个事件。{this. state.flag.tostring()}<br/>
        显示props<br/>
        {this.props.name}: (this.props.parent.state.pl + this.props.parent.state.p2}<br />
        {this.props.children}
        </div>;
    }
}

class Root extends React. Component {
    constructor (props) {
        super(props); // 一定要调用super父类构造器,否则报错
        this.state = { p1: 'www.magedu', p2: 'com'};  //构造函数中定义state
    }

    render() {
        // this.state.p1 = 'python.magedu"; // 可以修改属性值
        // this.setstate({p1:'python.magedu"}); //不可以对还在更新中的state使用setstate
        // Warning: setState(...): Cannot update during an existing state transition (such as within render).
        setTimeout (() => this.setState({ p1: 'python.gloo' }), 5000);
        // setInterval(() =› this.setState({ pl: 'python.gloo' }), 5000);
        return (
        <div>
            <div>Welcome to {this.state.p1}{this.state.p2}</div>
            <br />
            <Toggle name="school" parent={this}>{/*自定义2个属性通过props传给Togg1e组件对象*/}
                <hr />/*props. children*/
                <span>我是Toggle元素的子元素</span>{/*子元素通过props.children访问*/}
            </Toggle>
        </div>);
    }
}

ReactDom.render («Root />, document.getElementById('root')) ;

组件的生命周期*

组件的生命周期可分成三个状态:

  • Mounting :已插入真实 DOM
  • updating :正在被重新渲染
  • Unmounting :已移出真实 DOM

组件的生命周期状态,说明在不同时机访问组件,组件正处在生命周期的不同状态上。
在不同的生命周期状态访问,就产生不同的方法

生命周期的方法如下:

  • 装载组件触发
    - componentWilMount 在渲染前调用,在客户端也在服务端。只会在装载之前调用一次.
    - componentDidMount :在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,以通过this.getDOMNode0来进行访问。如果你想和其他Java Script框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异部操作阻塞UI)。只在装载完成后调用次,在render之后。

  • 更新组件触发。这些方法不会在首次render组件的周期调用
    - componentWillReceiveProps(nextProps)在组件接收到一个新的prop时被调用。这个方法在初始化render时不会被调用。
    - shouldcomponentUpdate(nextProps, nextstate) 返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。
    - 可以在你确认不需要更新组件时使用。
    - 如果设置为false,就是不允许更新组件,那么componentwillUpdate、componentDidUpdate不会会执行。
    - componentwillUpdate(nextProps, nextstate) 在组件接收到新的props者state但还没有render时被调用。在初始化时不会被调用。
    - componentDidupdate(prevProps, prevState) 在组件完成更新后立即调用。在初始化时不会被调用。

  • 卸载组件触发
    - componentWillUnmount在组件从 DOM 中移除的时候立刻被调用。
    image.png
    由图可知
    constructor构造器是最早执行的函数。
    触发 更新生命周期函数,,需要更新state或props。

因此,重新编写/src/indexjs。
构造两个组件,在子组件Sub中,加入所有生命周期函数。

下面的例子添加是装载、卸载组件的生命周期函数

import ReactDom from 'react-dom';

class Sub extends React. Component {
    constructor (props) {
        console.log('Sub constructor')
        super(props); // 调用父类构造器
        this.state = { count: 0 };
    }

    handleClick(event){
        this.setState({ count: this.state.count + 1 });
    }

    render() {
        console.log('Sub render');
            return (<div id="sub" onClick={this.handleClick.bind(this)}>
            Sub's count = {this.state.count}
            </div>);
    }

    componentwillMount(){
        // constructor之后,第一次render之前
        console.log('Sub componentwillMount');

    componentDidMount(){
        // 第一次render之后
        console.log('Sub componentDidMount');
    }

    componentwillUnmount(){
        // 清理工作
        console.log('Sub componentwillUnmount');
    }
}

class Root extends React. Component {
    constructor(props){
        console.1og('Root Constructor')
        super(props);//调用父类构造器
        // 定义一个对象
        this.state = {}
    }

    render() {
        return
        <div>
            <Sub />
        </div>);
    }
}

ReactDom.render(<Root />, document.getElementById('root'));

上面可以看到顺序是
constructor -> componentWillMount-> render -› componentDidMount ----staterkprops##-› render

增加更新组件函数
为了演示props的改变,为Root元素增加一个click事件处理函数

import React from 'react';
import ReactDom from 'react-dom';

class Sub extends React.Component {
    constructor(props) {
        console.log('Sub constructor')
        super(props);  // 调用父类构造器
        this.state = { count: 0 };
    }

    handleClick(event){
        this.setState({ count: this.state.count + 1 });
    }

    render() {
        console.log('Sub render');
        return (<div style={{ height: 200 + 'px', color: 'red', backgroundcolor: '#f0f0f0" }}>
        <a id="sub" onClick={this.handleClick.bind(this)}>
        Sub's count = {this.state.count}
        </a>
        </div>);
    }

    componentwillMount(){
        // constructor之后,第一次render之前
        console.log('Sub componentwillMount');

    componentDidMount(){
        // 第一次render之后
        console.log('Sub componentDidMount');
    }

    componentwillUnmount(){
        // 清理工作
        console.log('Sub componentwillUnmount');
    }

    componentWillReceiveProps(nextProps){
        // props变更时,接到新props了,交给shouldcomponentupdate。
        // props组件内只读,只能从外部改变
        console.log(this.props);
        console.log(nextProps);
        console.log('Sub componentwillReceiveProps', this.state.count);
    }

    shouldComponentUpdate(nextProps, nextState) {
        // 是否组件更新,props或state方式改变时,返回布尔值,true才会更新
        console.log('Sub shouldcomponentupdate', this.state.count, nextstate);
        return true; // return false将拦截更新
    }

    componentwillUpdate(nextProps, nextstate) {
        // 同意更新后,真正更新前,之后调用render
        console.log('Sub componentWillUpdate', this.state.count, nextState);
    }

    componentDidUpdate(prevProps, prevState){
        // 同意更新后,真正更新后,在render之后调用
        console.log('Sub componentDidUpdate', this.state.count, prevState);
    }
}

class Root extends React.Component {
    constructor(props) {
        console.log('Root Constructor')
        super (props); // 调用父类构造器
        // 定义一个对象
        this. state = { flag: true, name: 'root' };
    }

    handleClick(event){
        this.setstate({
            flag: !this.state.flag,
             name: this.state.flag? this.state.name.toLowerCase(): this.state.name.toUpperCase()
        });
    }

    render() {
        return(
        <div id="root" onClick={this.handleClick.bind(this)}>
        My Name is {this.state.name)
        <hr />
        <Sub />{/*父组件的render,会引起下一级组件的更新流程 ,导致props重新发送,即使子组件props没有
        </div>);
    }
}

ReactDom.render(<Root />, document.getElementById ('root')) ;

componentwillMount 第一次装载,在首次render之前。例如控制stateprops
componentDidMount 第一次装载结束,在首次render之后。例如控制state、props
componentWillReceiveProps 在组件内部,props是只读不可变的,但是这个函数可以接收到新的props,可以对
props做一些处理,this.props = tname: roooooot了;这就是偷梁换柱。componentWillReceiveProps触发,也会走shouldComponentUpdate.
shouldComponentupdate 判断是否需要组件更新,就是是否render,精确的控制渲染,提高性能。
componentWillUpdate 在除了首次render外,每次render前执行,componentDidupdate在render之后调用
不过大多数时候,用不上这些函数,这些钩子函数是为了精确的控制

无状态组件

React从15.0开始支持无状态组件,定义如下

import React from 'react';
import ReactDom from 'react-dom';

function Root(props) {
    return <div>(props.schoolName}</div>;
}
ReactDom. render (<Root schoolName= "magedu" /›, document.getElementById ('root')) ;

无状态组件,也叫函数式组件,
开发中,很多情况下,组件其实很简单,不需要state状态 ,也不需要使用生命周期函数。无状态组件很好的满足
了需要。
无状态组件函数应该提供一个参数props,返回一个React元素.
无状态组件函数本身就是render函数。
改写上面的代码

import React from 'react';
import ReactDom from 'react-dom';

let Root = props => <div>{props.name}</div>;

ReactDom.render(<Root schoolName="Gloo" /›, document.getElementyId('root'));