React简介虚拟DOMDiff算法创建React项目JSX语法组件组件声明方式组件传值props和state组件的生命周期

Posted 苦海123

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React简介虚拟DOMDiff算法创建React项目JSX语法组件组件声明方式组件传值props和state组件的生命周期相关的知识,希望对你有一定的参考价值。

React简介:

前面只是简单介绍移动APP开发,后面还会继续深入介绍移动app开发;其中想要用ReactNative开发出更出色的应用,那么就得学好React,下面将介绍React:

React 是一个由 Facebook 开发用于构建用户界面的渐进式 javascript 库,其特点:声明式设计、高效、灵活、JSX、组件化、单向数据流。

React也是组件化的,与vue不同的是:React直接使用JS代码编写组件(结构、样式、逻辑混合在js代码中)。

React是前端三大框架中诞生最早的框架,社区庞大,技术团队实力雄厚。

虚拟DOM:

操作原生DOM是一件非常耗性能的事,操作虚拟DOM就不会那么耗性能了,操作虚拟DOM时采用Different算法,只更新变化的虚拟DOM部分;虚拟DOM指通过程序员手动模拟出来类似原生DOM的对象,如下面模拟一个带有链接的p元素:

	var p = {//定义一个对象
      tagName:'p',//标签名为:p
      attrs:{//定义属性:
        class:'font16'
      },
      children:[//定义内容,相当于innerText
        '跳转到:',
        {//在父标签的children中再定义以对象,其方法和定义p一样:
          tagName:'a',//标签名为:a
          attrs:{//属性:
            href:'https://www.baidu.com',
          },
          children:[//定义内容:
            '百度'
          ]
        }
      ]
    }

Diff算法:

Diff算法用于对比新旧虚拟DOM的算法,其中有三部分:tree diff、component diff、element diff,其区别:

tree diff :新旧DOM树逐层对比的方式,对比所有层节点来找到被更新的节点后修改旧DOM。

component diff:组件之间的对比,当对比组件的时候,如果两个组件的类型相同,则这个组件暂时不需要被更新,如果组件的类型不同,则立即移除旧组件,新建一个组件,替换到被移除的位置。

element diff:组件中每个元素之间的对比。

使用虚拟DOM创建React项目(导入资源型):

使用React开发项目时,必须安装两个包:react、react-dom;react是用来创建组件、组件生命周期等。react-dom用来操作DOM。创建react项目步骤:

	//1.新建一个项目文件夹,并在文件夹打开终端键入:npm init -y 初始化一个package.json文件
    //2.终端输入:cnpm install react react-dom --save 安装react和react-dom到运行里,ReactNative开发中不建议使用cnpm装包
    //3.新建src文件夹并新建main.js文件并引入:react和react-dom:开始编写react中js主文件(并新建index.html文件,文件中留渲染的div)
    import React from 'react';
    import ReactDOM from 'react-dom';
    //4.在main.js文件中使用React提供的API操作元素:
      //4-1使用React.createElement()创建一个虚拟DOM,接收至少三个参数:参数1:字符串(标签类型),参数2:对象(标签属性),参数3开始:当前元素子节点,可放多个虚拟dom,如:
      var mydiv = React.createElement('div',{class:'mydiv',id:'box'},'这是一个div元素');//<div class='mydiv' id='box'>这是一个div元素</div>
    
      var spanp = React.createElement('span',{class:'spans'},'被span标签包裹的文本');//<span class='spans'>被span标签包裹的文本</span>
      var textp = React.createElement('p',{class:'tp'},'p标签文本中',spanp);//<p class='tp'>p标签文本中<span class='spans'>被span标签包裹的文本</span></p>
    
      // 4-2使用ReactDOM.render()将虚拟DOM渲染到页面中,参数1:渲染DOM内容,参数2:渲染的dom元素位置(获取DOM的方式),如:
      ReactDOM.render(mydiv,document.getElementById('app'));//这里app表示index.htlm文件中一个id值为app的标签,如<div id='app'></div>
    //4.webpack打包构建后,在dist目录下的文件是正常可以访问的。

JSX:

不难发现使用js创建元素的方式是非常繁琐的,因此这里介绍一款可以解决这个问题语法:JSX;

HTML 语言直接写在 JavaScript 语言中,不加任何引号,这就是 JSX 语法,它允许 HTML 与 JavaScript 的混写;

它是 一种 JavaScript 的语法扩展, 我们推荐在 React 中使用 JSX 来描述用户界面,JSX 是在 JavaScript 内部实现的;元素是构成 React 应用的最小单位,JSX 就是用来声明 React 当中的元素(底层实际就是通过上面js创建元素的)使用JSX语法时首先要安装:(cnpm install babel-preset-react -D)并配置在.babelrc文件中,babel-preset-react 用来转换JSX代码。(注意新版本:babel-preset-react-app,此环境应该基于上面环境)

与浏览器的 DOM 元素不同,React 当中的元素事实上是普通的对象,如:

	// 注意:想要正常运行JSX语法:还需要以下两步:
    //1.在项目目录下新建:.babelic文件,其配置代码如下:
    {
      "presets": ["env", "stage-0", "react"],
      "plugins": ["transform-runtime"]
    }
    //2.cnpm i babel-preset-env babel-preset-stage-0 babel-plugin-transform-runtime --save ,下载上面配置依赖的包。
    //3.配置完以上环境后,应该使用webpack打包后才可以以files的方式打正常访问react项目
    
    
    
    
    import React from 'react';
    import ReactDOM from 'react-dom';
    
    var titles = '这是一个提示';
    var elements =
            <div>{/* JSX语法中允许有一个根节点,根节点中可以嵌套其它元素 */}
                <p title={titles}>hello</p>{/*JSX中使用变量用{}包裹,实际指使用js语法时用{}包裹*/}
                <a href="#">hello</a>
          		  <p className='textp'>hello</p>{/*在JSX中使用className代替class属性,因为class在js中只一个关键字*/}
          			<label htmlFor="">hello</label>{/* 在JSX中for使用htmlFor代替 */}
            </div>
    const box = document.getElementById('box');
    ReactDOM.render(elements,box);
    
    //总结:
    //1.当编译时遇到尖括号<>当JSX执行,当遇到大括号{}当js执行
    //2.JSX语法中使用className代替class属性
    //3.在JSX中for使用htmlFor代替
    //4.jsx语法中只能使用一个根元素
    //5.jsx中数组会自动展开

JSX 中注释:

写法一:

	{
      // 注释
      // ...
    }

写法二(单行推荐):

	{/* 单行注释 */}

写法三(多行推荐):

	 {
      /*
       * 多行注释
       */
    }

JSX 不同写法:

Babel 会把 JSX 通过React.createElement() 函数编译,每个 React 元素都是一个真实的 JavaScript 对象;下面几种方式是等价的,如:

	const element = (
      <h1 className="greeting">
        Hello, world!
      </h1>
    );

    const element = React.createElement(
      'h1',
      {className: 'greeting'},
      'Hello, world!'
    );

    const element = {
      type: 'h1',
      props: {
        className: 'greeting',
        children: 'Hello, world'
      }
    };

    const element = {
      tagName:'h1',
      attrs:{className: 'greeting'},
      children:['Hello, world']
    }

组件:

React 允许将代码封装成组件,然后像插入普通 HTML 标签一样,在网页中插入这个组件即可使用;组件规则注意事项:组件名的第一个首字母必须大写,class声明式组件必须有 render 方法,组件内必须有且只有一个根节点,组件的属性可以在组件内通过props 获取(函数需要传递参数:props;类直接通过: this.props),如:

函数式声明组件(无状态):

名字不能用小写,React 在解析的时候,是以标签的首字母来区分的,如果首字母是小写则当作 HTML 来解析,如果首字母是大写则当作组件来解析。

	 // 函数名大写,组件名为函数名
    // 函数式声明组件,必须有return关键字,即使没内容也应该:return null
    function Header(props){
      return (//当返回多行代码时,建议使用小括号括起来
        <div>
          <p>这是header组件{props.name}</p>{/* 这里直接通过{属性名}的方式是不能拿到属性值的,需要给函数传递一个参数如:props等,在通过这个参数点出属性:{props.name},且这些属性只读 */}
        </div>
      )
    }
    
    const box = document.getElementById('box');
    // 通过 <函数名/> 的方式定义组件名;组件中传递参数使用属性的方式,如:name={变量名},age={变量名};分开传递参数很不方便,可以使用es6中属性扩散语法传递一个对象达到同样效果:
    
    var obj = {
      name:'jack',
      age:'28',
      gender:'男'
    }
    
    var cont = <div>
      <Header {...obj}/> {/* es6中属性扩散语法 */}
    </div>
    ReactDOM.render(cont,box);

抽离组件:

上面组件声明在同一个js文件中,没有达到减少主文件代码量的问题,想要减少主文件代码量,就得将组件抽离出来,如:

	 // 被抽离的组件header.js文件:(扩展:组件可以使用jsx后缀名,但是需要在webpack.config.js文件中配置解析jsx文件的loader:loader和js一样,可连写为:( js|jsx)$ 、jsx?$ )
    import React from 'react';//导入React,注意:React首字母必须大写
    
    function Header(props){
      return (
        <div>
          <p>这是header组件{props.name}</p>
        </div>
      )
    }
    // 抽离组件需要暴露出:
    export default Header;//或直接在函数或类前面加 export default;
    
    
    
    // 在主文件(main.js)中使用这个组件:
    // 导入组件:
    import Header from './components/header.js';
    // 在主文件中使用 <Header><Header/> (简写:<Header/>)使用组件即可

类方式声明组件(有状态):

	// 通过class定义一个Header组件,extends 继承了React中Component
    class Header extends React.Component { 
      render() { //使用render函数,函数中返回组件类容:
        return (
          <div>
              <p>这是header组件{this.props.names}</p>{/*class声明的组件拿传递的值通过:this.props点属性名,也是只可读的*/}
          </div>
        )
      }
    }; 
    ReactDOM.render(<Header names='jack'/>, document.getElementById('box'));
    // 抽离组件的方式和函数声明组件的方式中一样

组件传值props和state:

state 和 props区别在于props 是不可变的,而 state 可以改变。函数式组件只能通过 props 来传递数据,不能通过state传值;class定义的组件都可以使用它们传值。

	class Header extends React.Component { 
      constructor(props){//class定义的类中如果实现了继承,默认就有constructor函数,只是看不见;当写出这个构造函数时,要通过super调用:
        super(props);//表示父类的构造函数
        console.log(props);//在constructor中是不能直接使用props的,想要使用,就得在constructor中传递props
    
        // constructor中的this.state表示私有数据对象,类似vue中data(){},this.state中定义的数据是可改变的,通过this.state点属性拿到,如:
        this.state={
          messages:'hello',
          names:'jack'
        }
      }
    
      render() { 
        return (
          <div>
              <p>这是header组件{this.props}</p>
              <p>这是header组件{this.state.messages}</p>
              <button onClick={this.change}>变更messages的值</button>{/* jsx中使用事件时,必须使用驼峰命名法,且处理函数名前面加this. */}
          </div>
        )
      }
    
      //React函数中this默认指向undefined,若想this指向class类,可以使用箭头函数:如:
      // change(){
      //   this.state.messages = '修改hello为word';
      // }
      
      change=()=>{//这里是将箭头函数赋值给change,箭头函数中this依旧指向class类
        // this.state.messages = '修改hello为word';//通过this.state方式修改数据只是单向的,内存中数据是修改了,但是页面没有自动刷新,因此也不推荐,推荐setState()方法异步修改,如
    
        // this.setState({//setState()方法中传递一个对象,对象中传入要修改的属性,如:
        //   messages:'word',
        //   names:'lucky'
        // })
    
        // this.setState(function(prevState,props){//setState也支持传递一个函数,但是函数必须return一个对象;函数中第一个参数表示修改数据之前的数据,第二个参数是外界传递过来的数据属性
        //   return {
        //     messages:'word'
        //   }
        // })
    
        this.setState(function(prevState,props){//setState()因为setState是异步修改数据的,想要确保拿到最新的数据,那么,可以给setState继续传递一个参数为:回调函数,在回调函数里面拿到的数据是比较保险的,如:
          return {
            messages:'word'
          }
        },function(){
          console.log(this.state.messages);
        })
      }
    }; 
    
    ReactDOM.render(<Header/>, document.getElementById('box'));
    // 抽离组件的方式和函数声明组件的方式中一样

组件的生命周期:

组件从创建到运行再到销毁,这期间伴随着各种各样的事件,这些事件统称为组件的生命周期函数;

组件生命周期分为三部分:组件创建阶段(命周期函数一生只创建一次)、组件运行阶段(生命周期函数根据props和state的状态是否改变运行0-N次)、组件销毁阶段(生命周期函数一生只执行一次)。

	import React from 'react';
    import ReactDOM from 'react-dom';
    import ReactTypes from 'prop-types';//导入数据校验模块
    
    class Header extends React.Component { 
      constructor(props){
        super(props);
        
        //1.使用:this.state={}初始化组件私有状态,初始化组件私有数据
        this.state={
          messages:'hello',
          numstate:props.num//因为props中的值时只读的,想要修改值,就得使用state存值,但是state存的值是固定的,因此这里可以传递一个动态的值,即props的值,此时页面的值应该为对应的state值。
        }
      }
    
      //2.使用:static defaultProps = {}定义默认的值供程序正常运行,如:
      static defaultProps = {
        num:0
      }
    
      //3.使用:static propTypes = {}做数据校验,防止传过来的数据无效,如:
      static propTypes = {//特别注意:做校验需要安装一个包prop-types: npm install prop-types ,并在开头导入
        num:ReactTypes.number//如果需要检验其他数据类型,可阅读prop-types原文;当做完数据校验时,在组件中传递过来的值类型不符合时会报警告
      }
    
      // 4.使用:componentWillMount(){}组件即将被挂载到页面时触发函数,虚拟DOM也没创建(不能被拿到),因此此页面不能操作页面,可以操作数据,如:
      componentWillMount(){
        console.log(this.state.messages);//数据可被拿到
        console.log(this.props.num);//数据可被拿到
        this.myFunction();//函数可被调用
        console.log(document.getElementById('testp'));//不能被获取
      }
    
      // 5.使用:render(){}即将渲染内存中的虚拟DOM,该函数执行完后内存中已经渲染好数据了,但是还没有被挂载到页面上,如:
      render() { //render中不能使用this.setState方法,否则会进入死循环
        return (
          <div>
              {/* <p id='testp'>num的值:{this.props.num}</p> */}
              <p id='testp' onClick={this.addnumstate}>num的值自增为:{this.state.numstate}</p>{/*修改为可改变的state值*/}
              <span ref='spans'>hello</span>{/* 利用refs属性可以快速获取到该节点 */}
          </div>
        )
      }
    
      // 6.使用:componentDidMount(){}页面已经有可见的DOM元素了,此时可以拿到render中的DOM,如:
      componentDidMount(){
        console.log(document.getElementById('testp'));//可以获取DOM元素,可对其进行操作(原生DOM型,但是需要注意this指向问题,可使用箭头函数,但是不推荐原生方式),如:
        // document.getElementById('testp').οnclick=()=>{
        //   this.setState({
        //     numstate:this.state.numstate++
        //   })
        // }//推荐React中事件绑定的方式,如testp标签中
      }
    
      //7.使用:conmponentWillReceiveProps(){}当子组件的属性props改变时触发此方法,但是第一次渲染时不会被触发,如:
      conmponentWillReceiveProps(nextProps){//这里的数据是旧的,但是通过第一个参数nextProps.属性名,拿到的是最新的数据
        console.log('当外界传递过来新的props时,才会触发该事件执行');
        console.log(next.Props.num);
      }
    
      addnumstate=()=>{//使用箭头函数改变this指向为类的实例;注意:在dom中掉用次函数时在函数名前加this
        this.setState({
          numstate:this.state.numstate++
        })
      }
    
      myFunction(){
        console.log('测试componentWillMount是否可以调用外部函数');
      }
    
    // 以上是组件创建阶段生命周期,下面将介绍组件运行阶段生命周期:
    
      // 1.使用:shouldComponentUpdate(){}里面return一个布尔值,判断是否要更新页面上数据,当为true时可以改变页面上数据,当为false时不能改变页面上的数据;无论是true或false,内存中数据都是改变重新渲染了
      shouldComponentUpdate(nextProps,nextState){//此方法中可传入两个参数:第一个表示最新的props,第二个表示最新的state
        console.log(nextState.numstate);
        return true;//这里应该使用传入参数做出逻辑后return 布尔值
      }
    
      // 2.当shouldComponentUpdate中返回false时,会重新返回到shouldComponentUpdate执行,若果返回的是true,则执行下面的:componentWillUpdate(){}将要更新数据,此时页面和内存中数据都未被更新,如:
      componentWillUpdate(){//此时页面上数据都是旧的
        console.log(this.refs.spans.innerHTML);//使用refs获取ref设置的值
      }
    
      // 3.这里有一个运行时的render方法,但是里面数据h还是旧的。
    
      // 4.使用:componentDidUpdate(){}更新了页面和内存中的数据,此时数据都是最新的,如:
      componentDidUpdate(){//此时页面上数据都是新的
        console.log(this.refs.spans.innerHTML);
      }
    }; 
    
    ReactDOM.render(<Header num='校验时传入无效数据时,会报警告'/>, document.getElementById('box'));

对上面组件生命周期流程总结如下图:

在这里插入图片描述

提示:本文图片等素材来源于网络,若有侵权,请发邮件至邮箱:810665436@qq.com联系笔者删除。
笔者:苦海

以上是关于React简介虚拟DOMDiff算法创建React项目JSX语法组件组件声明方式组件传值props和state组件的生命周期的主要内容,如果未能解决你的问题,请参考以下文章

深入理解react中的虚拟DOMdiff算法

前端学习入门之React

[react] 为何说虚拟DOM会提高性能?

ReactiveNative学习之Diff算法

react特点和创建虚拟DOM

视频分享尚硅谷HTML5前端视频_React视频