欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

React的一些概念

程序员文章站 2022-04-22 19:01:29
...

对于初学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

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
原文: https://www.w3cplus.com/react/react-beginner-intro.html © w3cplus.com