React的一些概念

发布于 大漠

对于初学React的同学而言,这并不是一件易事。就拿我自己来说,都不知道从何下手,应该如何去学习才能开始使用React。就算你对React不陌生,学习React也常会碰到一些瓶颈。比如说新颖的概念、开发工具的使用、抽象的名词、快速变化的生态环境等等。也就是说,一旦开始学习React,你会发觉要学的东西越来越多,甚至可能还没开始碰到React就被这些东西给吓跑了(特别是对于初学者,听到这些东东就傻眼了)。

这篇文章不是来介绍怎么学习React,而是要让初学者或没学过React的同学对React的一些重要概念有所了解。

元素(Element)

Element对于我们来说并不陌生,中文常称之为元素,也就是HTML里的标签元素。比如:

<h1>W3cplus</h1>
<p>Des...</p>

这里的<h1><p>等等就是元素。这些元素都可以添加一些属性,比如说classidstyle以及一些自定义的属性data-*。这我们所说的Element是HTML,对于我们来说很好理解,但是...

React的Element是写在JavaScript的普通对象(Plain Object)

在理解这句话之前,先来看看下面的示例,就算你是初次接触React,也应该能猜到这是一个导航元素,而且其字体的颜色是blue

var app = <Nav color="blue" />

这看起来像是HTML的语法,其官方的叫法是JSX,和我们所了解的HTML是完全不同的。这个<Nav color="blue" />不是HTML的DOM节点,而是一个对象(Plain Object)。

在React中,其实要我们用物件去描述Element。下面的示例用来阐述React如何用物件描述一个按钮元素(button element),可以把type想成一般的tag名称、props想成是写在tag里的属性(attributes)。

{
    type:"button",
    props: {
        className: "btn btn-primary",
        children: {
            type: "b",
            props: {
                children: "OK!"
            }
        }
    }
}

但这种编码方式降低了其可读性,所以我们把它包装成像我们熟悉的HTML的语法(也就是JSX),但千万记得,不能别一般的HTML搞混了,底下这段JSX实质上是一个对象。

<button className="btn btn-primary">
    <b>OK!</b>
</button>

在当今的前端开发上,常常需要大量的操作DOM,没处理好的话,效率是非常低的。这也就是React不希望我们这样做。

React要我们用物件去描述Element,然后它自动帮我们处理DOM,这样的开发方式叫作声明式编程(Declarative Programming),也就是React说的What(描述想呈现的Elemnt),而不是说的How(要怎么去处理DOM)。

如此一来,React开发上主要工作是处理JavaScript的物件,这除了比操作DOM来得更轻量且方便之外,这也是JavaScript常做的事情。也就是说学习React其实也就是在学习JavaScript,就是处理描述Element的物件(如果学过React的同学,就知道这就是React中的state)。

数据呈现:JSX

前面提到过了,React的ELement虽然看上去像HTML,其实它的专业术语称之为JSX。那么在学习React之前,很有必要要了解一下JSX。

了解JSX

JSX可以看做是JavaScript的拓展,有点类似于XML。使用React可以进行JSX语法到JavaScript的转换。它不是新语言,也没不用改变JavaScript的语法,只是对JavaScript的拓展。当然,使用React开发并不一定非要使用JSX(感兴趣的可以看看这篇译文)。

前面说过,JSX语法跟XML或者HTML很相似,但有一些细节需要注意:

  • 属性表达式: 要使用JavaScript表达式作为属性值,只需把这个表达式用一对大括号{}包裹起来,不要用引号""
  • 嵌套其他组件:只需要引入相应组件,并将其作为一个标签插入JSX即可
  • Style属性:我们可以在JSX内部写CSS,不过要遵循一下格式
  • HTML转义: React默认会进行HTML转义,用以避免XSS攻击
  • 注释:只需要在一个标签的子节点内(非最外层)小心地用{}包围要注释的部分即可
  • JSX最外层标签必须是唯一的
  • React的JSX里约定分别使用首字母大、小写来区分本地组件的类和HTML标签
  • 由于JSX就是JavaScript,一些JavaScript的标识符,比如classfor这样的不建议作为XML属性名,作为替代,React DOM使用classNamehtmlFor来做对应的属性

上面清单所提到的一些示例:

// JSX内部写CSS格式

React.render(
    <button color="blue">
        <b style={color:"white",display: "block"}></b>
    </button>
);

// HTML转义

var content = "<strong>content</strong>>";

React.render(
    <div dangerouslySetInnerHtml = {{__html:content}}></div>,
    document.body
);

// HTML不转义

var content = "<strong>content</strong>";
React.render(
    <div>{content}</div>, 
    document.body
);

咱们一起再来看看一个完整的示例:

var TopTitle = React.createClass({
    getDefaultProps: function(){
        return {
            titleContent: "TopTitleBar"
        };
    },
    render: function() {
        return (
            <h1 id="topTitle" className="J_title">{this.props.titleContent}</h1>
        );
    }
});

React数据模型:props和state

在React中有两种类型的数据模型propsstate。那么这两个概念对于学习React也很重要。

this.props

React是通过内置虚拟DOM来操作,这个组件的输入被称为props(也就是properties缩写)。通过JSX语法进行参数传递。在组件中,这些属性是不可以直接改变的,也就是说**this.props**是只读的。

this.state

React把用户界面当作简单的状态机。把用户界面想像成拥有不同状态,之后渲染这些状态,这样可以轻松让用户界面和数据保持一致。

React里,只需要更新组件的state,然后根据新的state重新渲染用户界面(不要操作DOM)。React来决定如何最高效的更新DOM。

在React组件内部,通过this.state来获取state值。但在使用的过程中千万要注意,不要尝试使用this.state.xxx = "xxx"来强制修改state的值,这样将会引起一系列错误。就算需要修改state值,在React中是提供了一些操作state的方法:

setState()

setState()不会立即改变this.state,而是创建一个即将处理的state转变。在调用该方法之后获取this.state的值可能会得到现有的值,而不是最新设置的值。

setState()总是触发一次重绘,除非shouldComponentUpdate()中实现了条件渲染逻辑。如果使用可变的对象,但又不能在shouldComponentUpdate()中实现这种逻辑,仅在新state和之前的state存在差异的时候调用setState()可避免不必要的重新渲染。

replaceState()

类似于setState(),只不它只是负责删除已经存在的state键。

render

render()方法是必须的。顾名思义,它负责组件的渲染工作。当render被调用时,会去检测this.propsthis.state,并且返回一个元素(这个元素可以使原生的HTML Dom也可以是React Dom,或是你自己定义的一些复合的组件模块,当然也可以返回nullfalse来表明你不希望做任何渲染工作)。每次被调用时,render方法都会返回'相同'的结果,它不会去读、写DOM或是和浏览器做交互(例如使用setTimeout)。

注意:切记要保证render方法的洁净,不可以在render方法中修改组件的state。如果你希望和浏览器交互或者做更多事情,请在componentDidMount()方法或者其他生命周期的方法中去实现。

React组件

知道React中Element是什么,那了解React中的Component(组件)也就很好理解了。在React中是通过Element去描述Component,其本质上也可以说Component是Element。

React组件的特色

React中的Component有两大特色:

  • 封装Element tree(利于前端模块化)
  • 可以使用JavaScript Function、Class来写

下在是使用ES6的Arrow Function写的一个DeleteAccount组件:

const DeleteAccount = () => ({
    type: "div",
    props: {
        children: [{
            type: "p",
            props: {
                children: "Are you sure?"
            }
        },{
            type: DangerButton,
            props: {
                children: "Yep"
            }
        },{
            type: Button,
            props: {
                color: "blue",
                children: "Cancel"
            }
        }]
    }
});

type的描述可以知道其本身是一个div元素,props告诉他的子组件(children component)包含p元素及DangerButtonButton组件(前面提到过,type并不仅是一般的tag标签,也可以是一个React组件)。

当然,也可以使用JSX来提高React组件的可读性:

const DeleteAccount = () => (
    <div>
        <p>Are you sure?</p>
        <DangerButton>Yep</DangerButton>
        <Button color="blue">Cancel</Button>
    </div>
);

这里不是要告诉大家怎么学习React的语法,而是要告诉大家学习React最重要的事情:使用对象(Plain Object)去描述React的组件(Component)或元素(Element)

组件说明和生命周期

当我们调用React.createClass()方法创建一个组件类时,必须提供一个包含render方法的对象作为实参,当然也可以包含其他一些生命周期方法,不过他们是可选的。下面让我们按照生命周期的顺序,来依次介绍一下这些生命周期方法。

简单了解一下React组件的生命周期。React的组件生命周期,就如同人一样,也有生老病死,其大致可以分为七个周期:

React组件生命周期

  • Mounting: 取得组件的初始参数和状态,并且进行第一次的渲染
  • DOM Muntings Complete: 渲染完成后,DOM也会跟着更新,使用者可以看到最新的画面
  • Mounted: 组件已经更新完成,等待其他变化
  • Receiving state: 组件的状态state已经被更新,并且重新渲染了,所以回到第2个周期
  • Unmounting: 组件即将被移除
  • Unmounted: 组件的死亡阶段

除了知道React组件的生命周期之外,还需要了解生命周期相关的方法。

React组件生命周期

在实际开发当中,那又要如何使用这些方法呢?其实在开发中,可以直接在class中使用这些方法,这样你就可以拦截到组件的变化,并且会执行后面相应的动作,如:

var Demo = React.createClass ({
    getInitialState: function () {
        return {
            name: "W3cplus"
        };
    },
    getDefaultProps: function () {
        return {
            myName: "大漠"
        };
    },
    statics: {
        isUndefined: function (str) {
            return str === undefined;
        },
        isNumber: function (num) {
            return typeof(num) === "number";
        }
    },
    propTypes: {
        myName: React.PropTypes.string
    },
    componentWillReceiveProps: function () {
        console.log("组件收到props!");
    },
    componentDidUpdate: function () {
        console.log("render渲染更新完成!");
    },
    componentWillUpdate: function () {
        console.log("收到新的props或者state!");
    },
    shouldComponentUpdate: function () {
        console.log("收到新的props或者state!即将进行render方法");
    },
    componentWillMount: function () {
        console.log("初始化render执行前!");
    },
    componentDidMount: function () {
        console.log("初始化render渲染完成!");
    },
    componentWillUnmount: function () {
        console.log("DOM被移除");
    },
    render: function () {
    	console.log("渲染开始!");
        return (
            <div className="container">
                <h1>Hello {this.state.name}</h1>
                <h2>I'm {this.state.myName}</h2>
            </div>
        );
    }
});

比如下面这个组件示例:

class Counter extends React.Component {
    constructor(props, context) {
        super(props, context);
        this.state = { value: 0 };
    }

  	componentWillMount() {
    	console.log('1. Mounting: componentWillMount');
  	}

  	componentDidMount() {
    	console.log('3. Mounted: componentDidMount');
  	}
  
  	componentWillReceiveProps(nextProps, nextContext) {
    	console.log('4. Recieving Props: componentWillReceiveProps');
  	}

  	shouldComponentUpdate(nextProps, nextState, nextContext) {
    	console.log('5. Recieving State: shouldComponentUpdate');
    	return true;
  	}

  	componentWillUpdate(nextProps, nextState, nextContext) {
    	console.log('5. Recieving State: componentWillUpdate');
  	}

  	componentDidUpdate(prevProps, prevState, prevContext) {
    	console.log('3. Mounted: componentDidUpdate');
  	}

  	componentWillUnmount() {
    	console.log('5. Recieving State: componentWillUpdate');
  	}

  	render() {
    	console.log('render');
    	return (<h1>{this.state.value}</h1>);
  	}
}

下面简单介绍了这些方法的使用,如果你想了解更详细的信息,可以阅读官方文件

getInitialState()

  • 组件首次挂载之前调用一次(仅执行一次)
  • 用户初始化this.state
  • 其返回值将作为this.state的初始值

getDefaultProps()

  • 当组件类被创建时调用一次并缓存其返回值
  • 用于设置this.props的初始值
  • 如果其父组件没指定props中的某个键,那么getDefaultProps()的返回对象中的相应属性将会合并到this.props中作为初始值。也就是说,当父集没有传入props时,getDefaultProps()方法可以保证this.props.xxx有默认值,并且它的返回值将被缓存,我们可以直接使用props而不必重复编写一些无意义的代码
  • 由于该方法在任何实例创建之前调用,因此它不能依赖于this.props,其返回值将在全部实例中共享

propTypes

  • 随着应用不断壮大,确保组件被正确使用变得非常有必要。propTypes用于验证传入到组件的props,以此来判断传入数据的有效性。当向props中传入无效数据时,JavaScript控制台会抛出警告
  • React.PropTypes验证器支持下面这些类型。但为了保证性能,建议只在开发环境下验证propTypes

React.PropTypes 验证器支持的类型:

PropTypes : {
    'arrayKey' : React.PropTypes.array , // 数组;
    'boolKey' : React.PropTypes.bool , // 布尔值;
    'funcKey' : React.PropTypes.func , // 函数;
    'numberKey' : React.PropTypes.number , // 数字;
    'objKey' : React.PropTypes.object , // 对象;
    'stringKey' : React.PropTypes.string , // 字符串;
    'nodeKey' : React.PropTypes.node , //所有可以被渲染的对象(数字、字符串、DOM及包含上述三种类型的数组)
    'eleKey' : React.PropTypes.element , // React元素;
    'arrayOfKey' : React.PropTypes.arrayOf(React.PropTypes.number) , // 指定类型构成的数组
    'oneOfKey' : React.PropTypes.oneOf(['one','two']) , // 用来限制oneOfKey的值只能接受指定值
    'objWithShap' : React.PropTypes.shape({
    color : React.PropTypes.string,
    width : React.PropTypes.number 
    }), // 用于验证特定形状的参数对象
    ...
    // 以上这些prop在默认情况下都是可以忽略的。
    // 当在任意类型后面加上'isRequired',来使该prop不可为空。如:
    'appId' : React.PropTypes.number.isRequired,
    'anyTypeKey' : React.PropTypes.any.isRequired, // 用于验证不可为空的任意类型
    'customProp' : function(props, propName, componentName) {
        if (!/matchme/.test(props[propName])) {
            return new Error('认证失败')
        }
    }
}

mixins数组

  • 组件是React里复用代码的最佳方式,mixins数组可以让我们在多个组件间复用一些方法。大致等同于将mixins数组中的组件方法引入到this下,以便我们可以直接调用
  • 如果一个组件使用了多个mixin,并且有多个mixin定义了同样的生命周期方法,所有这些生命周期方法都保证会被执行到。其执行顺序是:先按照mixin引入顺序执行mixin里面的方法,最后执行组件内定义的方法

statics

  • statics对象允许我们定义一些能够被组件类调用的静态方法
  • 在这里定义的方法都是静态的,所以我们可以在任何组件实例创建之前调用它们
  • 这些方法不能获取组件的propsstate,但是却可以接受参数

componentWillMount

  • 在初始化渲染执行前立即调用且仅调用一次
  • 服务端和客户端都只会调用一次。如果在这个方法内部调用setState来改变this.state的值,更新后的this.state值将被render方法感知到,并且render只会执行一次,尽管state改变了

componentDidMount

  • 在初始化render渲染方法之后,立即调用一次,仅客户端有效(服务端不会调用)
  • 在生命周期中的这个时间点,组件拥有一个DOM展现,可以通过this.getDOMNode()来获取相应的DOM节点。我们可以在这个方法中与其他JavaScript框架进行集成、处理一些渲染后的逻辑(比如说绑定一些事件等)、发送Ajax请求或是设置定时器方法(setTimeout/setInterval)等

componentWillReceiveProps

  • 组件每次收到新的props时调用,初始化渲染时不会被调用
  • 该方法可以作为React在prop传入之后,render()方法执行之前更新state的合适时机。老的props可以通过this.props获取到
  • 在初始化渲染时,该方法不会被调用。在该方法中调用this.setState()

shouldComponentUpdate

  • 在收到新的props或者state,将要渲染之前调用
  • 当我们确定新的propsstate不需要导致组件更新时,应在此方法中return false;,也就是说当shouldComponentUpdate()返回false时,render()方法不会被执行(componentWillUpdate()componentDidUpdate()都将不会被调动),直到下一次state改变。默认情况下,shouldComponentUpdate()总是返回true
  • 该方法在初始化渲染时不会被调用,且在使用forceUpdate()时也不会被调用

componentDidUpdate

  • 在组件更新已经同步到DOM中之后立刻被调用。也就是在除了初始化render之后
  • 使用该方法可以在组件更新之后操作DOM元素。基本等同于componentDidMount(),唯一不同在于首次初始化render渲染完成后将执行componentDidMount()方法,而之后每次渲染完成都会执行componentDidUpdate()方法。我们可以使用this.getDOMNode()来访问DOM节点
  • 该方法不会在初始化渲染的时候调用

componentWillUnmount

  • 在组件从DOM中移除的时候立刻被调用
  • 我们可以在该方法中执行任何必要的清理,比如无效的定时器,或者清除在componentDidMount()中创建的DOM元素等

对于React组件的生命周期来说,我们需要了解的是:

  • React的组件是有生命周期的
  • 生命周期的每一个阶段,都会调用相对应的方法
  • 操作这些方法,可以拦截React组件的变化,并且做相应的处理

总结

当我们开发Web网站或应用时,前端架构变得越来越复杂,有大量的 DOM需要处理的状况之下,性能就开始会出现问题。使用React则可以帮助我们省下处理DOM的麻烦,因为使用React的话,从头到尾我们都没有必要去操作真正的DOM,我们要做的事情仅仅是处理物件(描述Element、Component的物件)。

因为React生态系统过于庞大,需要学习的东西越来越多,但要记住一件很重要的事情,学习React其实也就是学习JavaScript。同时使用React开发会使用到最基本的JavaScript及Web API(XHR或Fetch),当然如果你想快速完成一个案例或应用,那么React是一个很好的选择。

另外,React还有很多有趣的东西值得讨论和学习,而这篇文章只是涉及了React最基础的部分,也向大家阐述了学习React最重要的一件事情:React是处理物件而不是处理DOM

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:https://www.fedev.cn/react/react-beginner-intro.htmlZoom Lebron XII 12