• React[16x]框架


    React[16x]框架

    (1)React 简介

    A JavaScript library for building user interfaces (用于构建用户界面的JavaScript库)
    React生态:React + React-Router + Redux + Axios + Babel + Webpack

    (1.1)React 背景

    React 起源于 Facebook 的内部项目,因为该公司对市场上所有JavaScript MVC框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。

    React的社区也是非常强大的,随着React的普及也衍生出了更多有用的框架,比如ReactNativeReact VRReact从13年开始推广,现在已经推出16RC(React Fiber)这个版本,性能和易用度上,都有很大的提升。

    (1.2) 什么是React

    (~~~~)React是一个简单的javascript UI库,用于构建高效、快速的用户界面。它是一个轻量级库,因此很受欢迎。它遵循组件设计模式声明式编程范式函数式编程概念,以使前端应用程序更高效。它使用虚拟DOM来有效地操作DOM。它遵循从高阶组件到低阶组件的单向数据流

    声明式编程

    (~~~~)声明式编程是一种编程范式,它关注的是你要做什么,而不是如何做。它表达逻辑而不显式地定义步骤。这意味着我们需要根据逻辑的计算来声明要显示的组件。它没有描述控制流步骤。声明式编程的例子有HTML、SQL

    函数式编程

    (~~~~)函数式编程是声明式编程的一部分javascript中的函数是第一类公民,这意味着函数是数据,你可以像保存变量一样在应用程序中保存、检索和传递这些函数
    函数式编程有些核心的概念,如下:

    • 不可变性(Immutability)
    • 纯函数(Pure Functions)
    • 数据转换(Data Transformations)
    • 高阶函数 (Higher-Order Functions)
    • 递归
    • 组合

    React 中一切都是组件。 我们通常将应用程序的整个逻辑分解为小的单个部分。 我们将每个单独的部分称为组件。 通常,组件是一个javascript函数,它接受输入,处理它并返回在UI中呈现的React元素。

    (1.3) React 和 Vue 的对比

    这是前端最火的两个框架,虽然说React是世界使用人数最多的框架,但是就在国内而言Vue的使用者很有可能超过React。两个框架都是非常优秀的,所以他们在技术和先进性上不相上下。

    React.js相对于Vue.js它的灵活性和协作性更好一点,所以我在处理复杂项目或公司核心项目时,React都是我的第一选择。而Vue.js有着丰富的API,实现起来更简单快速,所以当团队不大,沟通紧密时,我会选择Vue,因为它更快速更易用,当然Vue也完全胜任于大型项目。

    (2) React 的优缺点

    (2.1) React 优点

    • 1.声明式设计 −React采用声明范式,可以轻松描述应用。

    • 2.高效 −React通过对DOM的模拟,最大限度地减少与DOM的交互。

    • 3.灵活 −React可以与已知的库或框架很好地配合。

    • 4.JSX − JSX JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。

    • 5.组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。

    • 6.单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。

    应用学习方面:

    • 生态强大:现在没有哪个框架比React的生态体系好的,几乎所有开发需求都有成熟的解决方案。
    • 上手简单: 你甚至可以在几个小时内就可以上手React技术,但是他的知识很广,你可能需要更多的时间来完全驾驭它。
    • 社区强大:你可以很容易的找到志同道合的人一起学习,因为使用它的人真的是太多了。

    (3) React 和 React Native

    React Native 看起来很像 React只不过其基础组件是原生组件而非 web 组件。要理解 React Native 应用的基本结构,首先需要了解一些基本的 React 的概念,比如 JSX语法、组件、state状态以及props属性。

    (3.1) React Native开发特点

    • 一次学习,随处编写:使用React Native可以为iOSAndroid操作系统开发应用程序,不同平台上的代码根据平台会有一些微小的区别。
    • 混合开发:React Native代码开发的模块与原生代码开发的模块可以双向通信、无缝衔接;
    • 高效的移动应用开发:

    (1)独特的UI实现框架
    (2)组件化开发
    (3)跨平台移植代码迅速
    (4)自动匹配不同屏幕大小的手机

    • 高效的移动应用开发调试
    • 高效的应用热更新
    • 有效降低移动应用安装包体积
    • 学习门槛低、开发难度低

    使用React Native开发的代价
    为了得到React Native开发的优点,使用React Native开发的APP也需要付出一定的代价。

    (1)内存消耗大

    使用React Native开发的程序运行所需的内存比原生代码开发的程序略多。

    (2)运行速度

    使用React Native开发的代码运行速度比原生代码略慢。
    React React Native 除了在编码表现层都使用 JSX 语法外,在 ReactReact Native 的底层都有 Virtual DOMDOM 之间的映射与转换,以实现了页面组件高效更新的前端表现。

    (3.2) React Native与React的关系及特点

    **React NativeReact的关系及特点 : **

    React是基础框架,是一套基础设计实现理念,开发者不能直接使用它来开发移动应用或网页。在React之上发展出了React.js框架用来开发网页,发展出来React Native用来开发移动应用。底层原理是相同的,都是使用js实现虚拟dom树来驱动页面的渲染,react是驱动HTML dom的渲染,react native是驱动原生组件的渲染。

    **React.js : **

    目的 是为了使前端的V层更具组件化,能更好的复用,它能够使用简单的html标签创建更多的自定义组件标签,内部绑定事件,同时可以让你从操作dom中解脱出来,只需要操作数据就会改变相应的dom

    二者都是基于组件(component)开发,然后组件和组件之间通过props传递方法,每个组件都有一个状态(state),当某个方法改变了这个状态值时,整个组件就会重绘,从而达到刷新。另外,说到重绘就要提到虚拟dom了,就是用js模拟dom结构,等整个组件的dom更新完毕,它会有一个diff的过程,对比出哪些组件发生了变化,然后才渲染到页面,简单来说只更新了相比之前改变了的部分,而不是全部刷新,所以效率很高。

    虚拟DOM(Virtual DOM)的机制:在浏览器端用Javascript实现了一套DOM API。基于React进行开发时所有的DOM构造都是通过虚拟DOM进行,每当数据变化时,React都会重新构建整个DOM树,然后React将当前整个DOM树和上一次的DOM树进行对比,得到DOM结构的区别,然后仅仅将需要变化的部分进行实际的浏览器DOM更新。而且React能够批处理虚拟DOM的刷新,在一个事件循环(Event Loop)内的两次数据变化会被合并。

    后续将详细学习研究React ative

    (4) React[16x]入门

    (4.1) React开发环境搭建

    在搭建React开发环境前需要安装Node(自行百度)

    (4.1.1) 安装Node.js

    使用React.js是可以用最原始的<Script>标签进行引入的,但是这实在是太low了,工作中也不会有这种形式进行引入。所以在学习的时候,我们就采用脚手架形式的安装。这就需要我们安装Node.js,用里边的npm来进行安装。
    安装Node只需要进入Node网站,进行响应版本的下载,然后进行双击安装就可以了。

    Node中文网址:http://nodejs.cn/ (建议你在这里下载,速度会快很多)

    注意:一定要正确下载对应版本,版本下载错误,可是没有办法使用

    Node.js 安装好以后,如果是Windows系统,可以使用 Win+R打开运行,然后输入cmd,打开终端(或者叫命令行工具)。

    node -v
    

    如果正确出现版本号,说明Node安装成功
    检查npm环境,正确出现版本号,说明npm也是没问题的

    npm -v
    

    (4.1.2) React脚手架搭建

    使用npm命令来安装脚手架工具:

    npm install -g create-react-app
    

    create-react-appReact官方出的脚手架工具.

    补充知识:

    npm install moduleName # 安装模块到项目目录下

    npm install -g moduleName # -g 的意思是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm config prefix 的位置。

    npm install --save moduleName # --save 的意思是将模块安装到项目目录下,并在package文件的dependencies节点写入依赖。

    npm install --save-dev moduleName # --save-dev 的意思是将模块安装到项目目录下,并在package文件的devDependencies节点写入依赖。

    (4.2) 创建React项目

    1. 准备工作

    创建文件夹 reactdom ,作为项目路径

    2. 创建项目
    通过脚手架创建React项目命令:项目名demo01

    create-react-app demo01   //用脚手架创建React项目
    

    3. 启动项目
    这个有一个小坑,启动项目一定要先进入项目目录,而不是我们创建的reactdom,
    使用一下命令运行即可:

    cd demo01   //等创建完成后,进入项目目录
    npm start   //预览项目,如果能正常打开,说明项目创建成功
    

    (4.2.1) 项目目录介绍

    1. 项目初始化目录:

    2. 项目顶层目录

    先从进入项目根目录说起,也就是第一眼看到的文件(版本不同,可能稍有不同)

    • README.md : 这个文件主要作用就是对项目的说明,已经默认写好了一些东西,你可以简单看看。如果是工作中,你可以把文件中的内容删除,自己来写这个文件,编写这个文件可以使用Markdown的语法来编写。
    • package.json :这个文件是webpack配置和项目包管理文件,项目中依赖的第三方包(包的版本)和一些常用命令配置都在这个里边进行配置,当然脚手架已经为我们配置了一些了,目前位置,我们不需要改动。如果你对webpack了解,对这个一定也很熟悉。
    • gitignore : 这个是git的选择性上传的配置文件,比如一会要介绍的node_modules文件夹,就需要配置不上传。
    • node_modules :这个文件夹就是我们项目的依赖包,到目前位置,脚手架已经都给我们下载好了,你不需要单独安装什么。
    • public :公共文件,里边有公用模板和图标等一些东西。
    • src : 主要代码编写文件,这个文件夹里的文件对我们来说最重要,都需要我们掌握。

    3. public文件夹

    这个文件都是一些项目使用的公共文件,也就是说都是共用的,我们就具体看一下有那些文件吧。

    • favicon.ico : 这个是网站或者说项目的图标,一般在浏览器标签页的左上角显示。

    • index.html :_ 首页的模板文件_,我们可以试着改动一下,就能看到结果。

    • mainifest.json移动端配置文件,这个会在以后的课程中详细讲解。

    4. src文件夹

    这个目录里边放的是我们开放的源代码,我们平时操作做最多的目录。

    • index.js : 这个就是项目的入口文件

    • index.css :这个是index.js里的CSS文件

    • app.js : 这个文件相当于一个方法模块,也是一个简单的模块化编程

    • serviceWorker.js : 这个是用于写移动端开发的,PWA必须用到这个文件,有了这个文件,就相当于有了离线浏览的功能。

    (4.3) Hello World和组件的编写

    先把src目录里的文件全部删除,我们一点点写一个HelloWorld程序,并通过编写这个程序了解一下什么是React中的组件化编程。

    (4.3.1) 编写入口文件

    在src文件夹下,新建index.js入口文件。

    (4.3.2) 导入相关包

    index.js文件中,导入必要的基础包

    import React from 'react'
    import ReactDOM from 'react-dom'
    import App from './App.js'
    
    ReactDom.render(<App/>,document.getElementById('root'))
    

    首先引入React两个必要的文件,然后引入一个App组件(目前这个组件还没有编写,需要一会创建),然后用React的语法把App模块渲染到Root ID上rootID是在 publicindex.html 文件中的真实DOM

    (4.3.3) 编写App组件

    import React, {Component} from 'react'
    
    class App extends Component {
      render(){
        return(
        	<div>
        		Hello Word!!!
        	</div>
        )
      }
    }
    export default App;
    
    

    这里导入的包,导入形式有些不同,

    //ES6的语法-解构赋值
    import React, {Component} from 'react'
    
    //分开写
    import React from 'react'
    const Component = React.Component
    

    注意:

    React的主要优势之一就是组件化编写,这也是现代前端开发的一种基本形式
    创建React组件时,必须使用大写字母开头。一般文件名和组件名相同

    (4.3.4) 启动项目

    在终端,进入项目目录,使用npm start命令启动项目,我们可以在页面上看到Hello Word

    (4.4) React中JSX语法

    JSX语法,看起来跟html标签几乎一样

    (4.4.1) JSX简介

    JSX就是JavascriptXML结合的一种格式。React发明了JSX,可以方便的利用HTML语法来创建虚拟DOM,当遇到<JSX就当作HTML解析,遇到{就当JavaScript解析.

    比如我们写一段JSX语法:

    <ul className="my-list">
        <li>JAVA</li>
        <li>I love React</li>
    </ul>
    

    就等于之前我们写一段JS代码:

    var child1 = React.createElement('li', null, 'JSPang.com');
    var child2 = React.createElement('li', null, 'I love React');
    var root = React.createElement('ul', { className: 'my-list' }, child1, child2);
    

    从代码量上就可以看出JSX语法大量简化了我们的工作。

    (4.4.2) 组件和普通JSX语法的区别

    这个说起来也只有简单的一句话,就是你自定义的组件必须首写字母要进行大写,而JSX是小写字母开头的。

    (4.4.3) JSX使用三元越算符

    import React from 'react'
    const Component = React.Component
    
    class App extends Component{
      render(){
        return(
        	<ul className = 'my-list'>
        		<li>{Hello Word ? '你好':'欢迎你'}</li> //三目运算
        		<li> I  love  React </li>
        	</ul>
        )
      }
    }
    export default App;
    

    (4.5) React 实例学习

    (4.5.1) 新建组件【小姐姐组件】

    现在src的目录下面,新建一个文件Xiaojiejie.js文件,然后写一个基本的HTML结构。代码如下:

    import React,{Component} from 'react'
    
    class Xiaojiejie exetends Component{
      render(){
        return(){
          <div>
          	 <div>
          		<input/>
          		<button>增加服务</button>
          	 </div>
          	 <ul>
          	 	<li>头部按摩</li>
          	 	<li>精油推背</li>
          	 </ul>
          </div>
        }
      }
    }
    export default Xiaojiejie
    

    然后我们把入口文件的<App />组件换成Xiajiejie组件。

    (4.5.1.1) 组件外层包裹原则

    这是一个很重要的原则,比如上面的代码,我们去掉最外层的<Div>,就回报错,因为React要求必须在一个组件的最外层进行包裹。
    所以我们在写一个组件的时候,组件的最外层都需要有一个包裹。

    (4.5.1.2) Fragment标签讲解

    加上最外层的DIV,组件就是完全正常的,但是你的布局就偏不需要这个最外层的标签怎么办?比如我们在作Flex布局的时候,外层还真的不能有包裹元素。这种矛盾其实React16已经有所考虑了,为我们准备了<Fragment>标签。

    要想使用<Fragment>,需要先进行引入。

    import React,{Component,Fragment} from 'react'
    

    然后把最外层的<div>标签,换成<Fragment>标签,代码如下:

    import React,{Component,Fragment } from 'react'
    
    class Xiaojiejie extends Component{
        render(){
            return  (
                <Fragment>
                   <div><input /> <button> 增加服务 </button></div>
                   <ul>
                       <li>头部按摩</li>
                       <li>精油推背</li>
                   </ul> 
                </Fragment>
            )
        }
    }
    export default Xiaojiejie 
    

    这时候你再去浏览器的Elements中查看,就回发现已经没有外层的包裹了。

    (4.5.2) React 响应式设计和数据绑定

    (4.5.2.1) React 响应式设计

    React不建议你直接操作DOM元素,而是要通过数据进行驱动,改变界面中的效果。React会根据数据的变化,自动的帮助你完成界面的改变。所以在写React代码时,你无需关注DOM相关的操作,只需要关注数据的操作就可以了(这也是React如此受欢迎的主要原因,大大加快了我们的开发速度)
    现在的需求是增加小姐姐的服务项,就需要先定义数据。数据定义在Xiaojiejie组件中的构造函数里constructor

    //js的构造函数,由于其他任何函数执行
    constructor(props){
      super(props)//调用父类的构造函数,固定写法
      this.state = {
        inputValue:'',//input中的值
        list:[]//服务列表
      }
    }
    

    React中的数据绑定和Vue中几乎一样,也是采用字面量(我自己起的名字)的形式,就是使用{}来标注,其实这也算是js代码的一种声明。比如现在我们要把inputValue值绑定到input框中,只要写入下面的代码就可以了。其实说白了就是在JSX中使用js代码。

    <input value={this.state.inputValue} /> 
    

    现在需要看一下是不是可以实现绑定效果,所以把inputValue赋予一个'jspang',然后预览看一下效果。在这里我们并没有进行任何的DOM操作,但是界面已经发生了变化,这些都时React帮我们作的,它还会自动感知数据的变化

    (4.5.2.2) React 绑定事件

    这时候你到界面的文本框中去输入值,是没有任何变化的,这是因为我们强制绑定了inputValue的值。如果要想改变,需要绑定响应事件,改变inputValue的值。比如绑定一个改变事件,这个事件执行inputChange()(当然这个方法还没有)方法。

     <input value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
    

    现在还没有inputChange()这个方法,在render()方法的下面建立一个inputChange()方法,代码如下:

    inputChange(e){
        // console.log(e.target.value);
        // this.state.inputValue=e.target.value;
        this.setState({
            inputValue:e.target.value
        })
    }
    

    注意点:
    1.一个是this指向不对,你需要重新用bind设置一下指向(ES6的语法)。
    2.另一个是React中改变值需要使用this.setState方法。

    (4.5.3) React 列表渲染

    (4.5.3.1) 遍历数组

    现在的列表还是写死的两个<li>标签,那要变成动态显示的,就需要把这个列表先进行数据化,然后再用javascript代码,循环在页面上。
    ist数组增加两个数组元素,代码如下:

    constructor(props){
        super(props) //调用父类的构造函数,固定写法
        this.state={
            inputValue:'jspang' , // input中的值
            //----------主要 代码--------start
            list:['基础按摩','精油推背']   
            //----------主要 代码--------end
        }
    }
    

    有了数据后,可以在JSX部分进行循环输出,代码如下:

    render(){
        return  (
            <Fragment>
                <div>
                    <input value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
                    <button> 增加服务 </button>
                </div>
                <ul>
                    {
                        this.state.list.map((item,index)=>{
                            return <li>{item}</li>
                        })
                    }
                </ul>  
            </Fragment>
        )
    }
    

    完成上面的步骤,数据就不再是固定的了,而是动态管理的

    解决 Key值报错:
    打开浏览器的控制台F12,可以清楚的看到报错了。这个错误的大概意思就是缺少key值。就是在用map循环时,需要设置一个不同的值,这个时React的要求。

    <ul>
        {
            this.state.list.map((item,index)=>{
                return <li key={index+item}>{item}</li>
            })
        }
    </ul>  
    
    (4.5.3.2) 增加数组元素

    要增加服务选项,我们需要再增加按钮上先绑定一个方法this.addList(这个方法目前还没有,需要我们接下来建立).

    <button onClick={this.addList.bind(this)}> 增加服务 </button>
    

    接下来就是把this.addList方法,代码如下:

    //增加服务的按钮响应方法
    addList(){
        this.setState({
            list:[...this.state.list,this.state.inputValue]
        })
    
    }
    

    注意:
    ...这个是ES6的新语法,叫做扩展运算符。意思就是把list数组进行了分解,形成了新的数组,然后再进行组合

    (4.5.3.3) 删除数组元素

    如果要删除一个东西,就要得到数组里的一个编号,这里指下标。传递下标就要有事件产生,先来绑定一个双击事件.代码如下:

    <ul>
        {
            this.state.list.map((item,index)=>{
                return (
                    <li 
                        key={index+item}
                        onClick={this.deleteItem.bind(this,index)}
                    >
                        {item}
                    </li>
                )
            })
        }
    </ul> 
    

    为了看着更清晰,我们在return部分加了()这要就可以换行编写JSX代码了.在onClick我们绑定了deleteItem方法.

    获得了数据下标后,删除数据就变的容易起来.先声明一个局部变量,然后利用传递过来的下标,删除数组中的值.删除后用setState更新数据就可以了.

    //删除单项服务
    deleteItem(index){
        this.state.list.splice(index,1)
        this.setState({
            list:this.state.list
        }) 
    }
    

    注意:

    记住React是禁止直接操作state的,虽然上面的方法也管用,但是在后期的性能优化上会有很多麻烦,所以一定不要这样操作

    (4.5.4) JSX语法注意事项

    ** JSX的注释,可以有下面两种写法:**

    <Fragment>
        {/* 正确注释的写法 */}
        <div>
            <input value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
            <button onClick={this.addList.bind(this)}> 增加服务 </button>
        </div>
    

    如果你记不住,有个简单的方法,就是用VSCode的快捷键,直接按Ctrl+/,就会自动生成正确的注释了。

    你可以把这个理解为,在jsx中写javascript代码。所以外出我们套入了{},然后里边就是一个多行的javascript注释。如果你要使用单行祝注释//,你需要把代码写成下面这样。

    <Fragment>
        {
            //正确注释的写法 
        }
        <div>
            <input value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
            <button onClick={this.addList.bind(this)}> 增加服务 </button>
        </div>
    
    1. class换成className,它是防止和js中的class类名 冲突,所以要求换掉
    2. JSX中Html解析问题
      如果想在文本框里输入一个<h1>标签,并进行渲染。默认是不会生效的,只会把<h1>标签打印到页面上,这并不是我想要的。如果工作中有这种需求,可以使用dangerouslySetInnerHTML属性解决。具体代码如下:
    <ul>
        {
            this.state.list.map((item,index)=>{
                return (
                    <li 
                        key={index+item}
                        onClick={this.deleteItem.bind(this,index)}
                        dangerouslySetInnerHTML={{__html:item}}
                    >
                    </li>
                )
            })
        }
    </ul> 
    

    上面的代码就可以实现html格式的输出。
    4. JSX
    label是html中的一个辅助标签,也是非常有用的一个标签。

    先看下面的代码,我们在文本框前面加入一个<label>

    <div>
        <label>加入服务:</label>
        <input className="input" value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
        <button onClick={this.addList.bind(this)}> 增加服务 </button>
    </div>
    

    这时候想点击“加入服务”直接可以激活文本框,方便输入。按照html的原思想,是直接加ID就可以了。代码如下:

    <div>
        <label for="jspang">加入服务:</label>
        <input id="jspang" className="input" value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
        <button onClick={this.addList.bind(this)}> 增加服务 </button>
    </div>
    

    这时候你浏览效果虽然可以正常,但console里还是有红色警告提示的。大概意思是不能使用for它容易和javascript里的for循环混淆,会提示你使用htmlfor

    <div>
        <label htmlFor="jspang">加入服务:</label>
        <input id="jspang" className="input" value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
        <button onClick={this.addList.bind(this)}> 增加服务 </button>
    </div>
    

    这时候代码就正确了,可以实现点击<label>后,激活<input>标签了。

    (4.5.5) Simple React Snippets插件

    vscode中的Simple React Snippets

    1. 安装
    打开SCode的插件查单,然后在输入框中输入imple React Snippets然后点击进行安装就可以了

    2. 快速进行引入import
    直接在vscode中输入imrc,就会快速生成最常用的import代码。

    import React, { Component } from 'react';
    

    2. 快速生成class
    在作组件的时候,都需要写一个固定的基本格式,这时候你就可以使用快捷键cc插件就会快速帮我们生成如下代码

    class  extends Component {
        state = {  }
        render() { 
            return (  );
        }
    }
    
    export default ;
    

    更多详细快捷键,打开插件的说明文件看一下就可以了

    (4.5.6) 组件拆分

    小姐姐服务菜单已经完美的制作好了,但是这从头到尾我们只用了一个组件,但是在实际工作中肯定是团队开发,我们会把一个大功能分成不同的组件。比如把文本框和按钮单独一个组件,把下面的服务列表单独一个组件,这涉及了一个组件拆分的能力和知识

    1. 新建服务菜单组件
    src目录下,新建一个文件,这里就叫做XiaojiejieItem.js

    import React, { Component } from 'react'; //imrc
    class XiaojiejieItem  extends Component { //cc
    
        render() { 
            return ( 
                <div>小姐姐</div>
             );
        }
    }
    export default XiaojiejieItem;
    

    到以前写的Xiaojiejie.js文件中用import进行引入,代码如下:

    import XiaojijieItem from './XiaojiejiItem'
    

    2. 修改Xiaojiejie组件
    已经引入了新写的组件,这时候原来的代码要如何修改才能把新组件加入?

    把原来的代码注释掉,当然你也可以删除掉,我这里就注释掉了,注释方法如下:

    
    {/*
    <li 
        key={index+item}
        onClick={this.deleteItem.bind(this,index)}
        dangerouslySetInnerHTML={{__html:item}}
    >
    
    </li>
    */ }
    

    然后在最外层加入包裹元素<div>,为的是防止两个以上的标签,产生错误信息。

    最后直接写入Xiaojiejie标签就可以了.

    <XiaojiejieItem />
    

    完整代码:

    
    import React,{Component,Fragment } from 'react'
    import './style.css'
    import XiaojiejieItem from './XiaojiejieItem'
    
    class Xiaojiejie extends Component{
    //js的构造函数,由于其他任何函数执行
    constructor(props){
        super(props) //调用父类的构造函数,固定写法
        this.state={
            inputValue:'' , // input中的值
            list:['基础按摩','精油推背']    //服务列表
        }
    }
    
    render(){
        return  (
            <Fragment>
                {/* 正确注释的写法 */}
    <div>
        <label htmlFor="jspang">加入服务:</label>
        <input id="jspang" className="input" value={this.state.inputValue} onChange={this.inputChange.bind(this)} />
        <button onClick={this.addList.bind(this)}> 增加服务 </button>
    </div>
                <ul>
                    {
                        this.state.list.map((item,index)=>{
                            return (
                                //----------------关键修改代码----start
                                <div>
                                    <XiaojiejieItem />
                                </div>
                                //----------------关键修改代码----end
    
                            )
                        })
                    }
                </ul>  
            </Fragment>
        )
    }
    
        inputChange(e){
            // console.log(e.target.value);
            // this.state.inputValue=e.target.value;
            this.setState({
                inputValue:e.target.value
            })
        }
        //增加服务的按钮响应方法
        addList(){
            this.setState({
                list:[...this.state.list,this.state.inputValue],
                inputValue:''
            })
    
        }
    //删除单项服务
    deleteItem(index){
        let list = this.state.list
        list.splice(index,1)
        this.setState({
            list:list
        })
    
    }
    
    }
    export default Xiaojiejie 
    

    (4.5.7)React-父子组件的传值

    上面已经把"小姐姐"组件做了一个基本的拆分,但是还不能实现随着输入,显示出输入的内容。这里涉及的是父组件向子组件传值。然后点击删除,就相当于子组件向父组件传值。

    (4.5.7.1)父组件向子组件传参

    这里只介绍最实用的,最快速的上手方法。就是使用组件属性的形式父组件给子组件传值。比如:我们在<XiaojiejieItem>组件中加入content属性,然后给属性传递{item},这样就完成了父组件向子组件传值。

    <XiaojiejieItem content={item} />
    

    现在值已经顺利的传递了过去,这时候可以通过this.props.xxx的形式进行接受,比如传递过来的值,可以用如下代码进行接收。

    import React, { Component } from 'react'; //imrc
    class XiaojiejieItem  extends Component { //cc
    
        render() { 
            return ( 
                <div>{this.props.content}</div>
             );
        }
    }
    
    export default XiaojiejieItem;
    

    修改完小姐姐子项的组件后,可以打开浏览器进行预览了。试着添加几个新的选项试一下
    父组件向子组件传递内容,靠属性的形式传递。

    (4.5.7.2)子组件向父组件传参

    现在要作这样一个功能:点击组件中的菜单项后,删除改菜单项。这就涉及了一个字组件向父组件传递数据的知识需要掌握。

    先来绑定点击事件,这时候当然是要在XiaojiejieItem组件中绑定了,代码如下:

    import React, { Component } from 'react'; //imrc
    class XiaojiejieItem  extends Component { //cc
    
        render() { 
            return ( 
                <div onClick={this.handleClick}>{this.props.content}</div>
             );
        }
    
        handleClick(){
            console.log('撩拨了小姐姐')
        }
    
    }
    
    export default XiaojiejieItem;
    

    这时候进行预览,打开F12,再点击服务菜单项,就会再console里显示出"撩拨了小姐姐"的字样。但是console里还有一个warning警告,这个警告我们见过,就是要求循环时必须设置key值。

    修改XiaoJieJie组件的render代码如下:

    <ul>
        {
            this.state.list.map((item,index)=>{
                return (
                    <XiaojiejieItem 
                    key={index+item}  
                    content={item} />
                )
            })
        }
    </ul> 
    

    绑定成功后,现在就要通过操作子组件删除父组件里的数据了。但是React有明确规定,子组件时不能操作父组件里的数据的,所以需要借助一个父组件的方法,来修改父组件的内容。其实在以前已经写了一个删除方法deleteItem,现在要作的就是子组件调用这个方法。

    //删除单项服务
    deleteItem(index){
        let list = this.state.list
        list.splice(index,1)
        this.setState({
            list:list
        })
    
    }
    

    1. 获取数组索引下标
    那现在问题来了,要删除就要知道索引值,还是需要通过父组件传递给子组件.这里还是通过props属性的形式进行传递。

    <ul>
        {
            this.state.list.map((item,index)=>{
                return (
                    <XiaojiejieItem 
                    key={index+item}  
                    content={item}
                    index={index} />
                )
            })
        }
    </ul>  
    

    然后修改XiaojiejieItem组件,在handleClick方法里,写入下面代码:

    return ( 
        <div onClick={this.handleClick.bind(this)}>
            {this.props.content}
        </div>
    );
    

    构造函数里绑定的。(有言曰:构造函数中绑定性能会高一些,特别是在高级组件开发中,会有很大的作用)
    constructor绑定this方法。

    
    import React, { Component } from 'react'; //imrc
    class XiaojiejieItem  extends Component { //cc
       //--------------主要代码--------start
       constructor(props){
           super(props)
           this.handleClick=this.handleClick.bind(this)
       }
       //--------------主要代码--------end
        render() { 
            return ( 
                <div onClick={this.handleClick}>
                    {this.props.content}
                </div>
            );
        }
        handleClick(){
            console.log(this.props.index)
        }
    }
    
    export default XiaojiejieItem;
    

    2. 子组件调用父组件方法
    如果子组件要调用父组件方法,其实和传递数据差不多,只要在组件调用时,把方法传递给子组件就可以了,记得这里也要进行this的绑定,如果不绑定子组件是没办法找到这个父组件的方法的。

    <ul>
        {
            this.state.list.map((item,index)=>{
                return (
                    <XiaojiejieItem 
                    key={index+item}  
                    content={item}
                    index={index}
                    //关键代码-------------------start
                    deleteItem={this.deleteItem.bind(this)}
                    //关键代码-------------------end
                    />
                )
            })
        }
    </ul>  
    

    传递后,在XiaojiejieItem组件里直接hi用就可以了,代码如下:

    handleClick(){
        this.props.deleteItem(this.props.index)
    }
    

    (4.5.8)React-单项数流

    1. 单项数据流
    React的特性中有一个概念叫做“单项数据流”,可能刚刚接触React的小伙伴不太明白这个概念,还是拿出《小姐姐服务菜单》的Demo,来给大家讲解。比如我们在父组件中可以直接把this.state.list传递过来。例如下面代码:

    <ul>
        {
            this.state.list.map((item,index)=>{
                return (
                    <XiaojiejieItem 
                    key={index+item}  
                    content={item}
                    index={index}
                    list={this.state.list}
                    deleteItem={this.deleteItem.bind(this)}
                    />
                )
            })
        }
    </ul> 
    

    其实这样传是没有问题的,问题是你只能使用这个值,而不能修改这个值,如果你修改了,比如我们把代码写成这样:

    handleClick(){
        //关键代码——---------start
        this.props.list=[]
        //关键代码-----------end
        this.props.deleteItem(this.props.index)
    }
    

    就会报下面的错误;

    TypeError: Cannot assign to read only property 'list' of object '#<Object>'
    

    意思就是list是只读的,单项数据流。那如果要改变这里边的值怎么办?其实上节课已经讲过了,就是通过传递父组件的方法。

    2. React和其他框架配合使用

    是可以的,React其实可以模块化和组件化开发。

    /public/index.html文件,代码如下:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta name="theme-color" content="#000000" />
    
        <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    
        <title>React App</title>
      </head>
      <body>
        <noscript>You need to enable JavaScript to run this app.</noscript>
        <!--关键代码start-->
        <div id="root"></div>
         <!--关键代码end-->
    
      </body>
    </html>
    

    其实React只对这一个<div>,外边的其他DOM并不受任何影响,比如我们在它的下方再写一个<div>,然后查看效果。

    <div id="root"></div>
    <div style="color:red">今天过的好开心,服务很满意!</div>
    

    你可以在其他的div里加入任何内容,但是这种情况很少,不建议这么使用。希望小伙伴们还是统一技术栈。

    3. 函数式编程
    在面试React时,经常会问道的一个问题是:函数式编程的好处是什么?

    • 函数式编程让我们的代码更清晰,每个功能都是一个函数。
    • 函数式编程为我们的代码测试代理了极大的方便,更容易实现前端自动化测试
      React框架也是函数式编程,所以说优势在大型多人开发的项目中会更加明显,让配合和交流都得心应手。

    (4.5.9) React developer tools

    React在浏览器端是有一个调试工具的,这就是React developer tools,这个是React人必下的一个调试工具

    React developer tools有三种颜色,三种颜色代表三种状态:

    • 灰色: 这种就是不可以使用,说明页面不是又React编写的。
    • 黑色: 说明页面是用React编写的,并且处于生成环境当中。
    • 红色: 说明页面是用React编写的,并且处于调试环境当中。

    打开浏览器,然后按F12,打开开发者工具,然后在面板的最后一个,你会返现一个React,这个就是安装的插件了。

    (4.5.10) PropTypes 校验传值

    在父组件向子组件传递数据时,使用了属性的方式,也就是props,但“小姐姐服务菜单”的案例并没有任何的限制。这在工作中时完全不允许的,因为大型项目,如果你不校验,后期会变的异常混乱,业务逻辑也没办法保证。

    1. PropTypes的简单应用
    我们在Xiaojiejie.js组件里传递了4个值,有字符串,有数字,有方法,这些都是可以使用PropTypes限制的。在使用需要先引入PropTypes

    import PropTypes from 'prop-types'
    

    引入后,就可以在组件的下方进行引用了,需要注意的是子组件的最下面(不是类里边),写入下面的代码:

    XiaojiejieItem.propTypes={
        content:PropTypes.string,
        deleteItem:PropTypes.func,
        index:PropTypes.number
    }
    

    具体意思,我会在视频中进行讲解,请观看视频。为了防止你的为止写错,我这里给出这个XiaojiejieItem.JS文件的代码。

    import React, { Component } from 'react'; //imrc
    import PropTypes from 'prop-types'
    
    class XiaojiejieItem  extends Component { //cc
    
       constructor(props){
           super(props)
           this.handleClick=this.handleClick.bind(this)
       }
    
        render() { 
            return ( 
                <div onClick={this.handleClick}>
                    {this.props.content}
                </div>
            );
        }
    
        handleClick(){
    
            this.props.deleteItem(this.props.index)
        }
    
    }
     //--------------主要代码--------start
    XiaojiejieItem.propTypes={
        content:PropTypes.string,
        deleteItem:PropTypes.func,
        index:PropTypes.number
    }
     //--------------主要代码--------end
    export default XiaojiejieItem;
    

    这时候你在浏览器中查看效果,是什么都看不出来的,你需要修改一个错误的校验。比如我们把index改为必须是字符串

    index:PorpTypes.string
    

    这时候浏览器的console里就会报错了,报错信息如下:

    Warning: Failed prop type: Invalid prop `index` of type `number` supplied to `XiaojiejieItem`, expected `string`.
        in XiaojiejieItem (at Xiaojiejie.js:28)
        in Xiaojiejie (at src/index.js:5)
    

    意思就是要求传递字符串,而我们却传递了数字过去,所以给了警告。

    必传值的校验:
    比如现在我们加入一个avname的属性,并放入JSX中,就算不传递这个值也不会报错的。代码如下:

    render() { 
        return ( 
            <div onClick={this.handleClick}>
                {this.props.avname}为你做- {this.props.content}
            </div>
        );
    }
    

    这时候代码是不会报错的,我们传不传无所谓。比如我们现在传一个属性过来。

    <ul>
        {
            this.state.list.map((item,index)=>{
                return (
                    <XiaojiejieItem 
                    key={index+item}  
                    content={item}
                    index={index}
                    avname='你好'
                    deleteItem={this.deleteItem.bind(this)}
                    />
                )
            })
        }
    </ul> 
    

    这时候页面显示正常了,但是怎样避免必须传递avname这个属性值?如果不传递就报错,这就需要使用isRequired关键字了,它表示必须进行传递,如果不传递就报错。

    avname:PropTypes.string.isRequired
    

    使用默认值defaultProps:
    defalutProps就可以实现默认值的功能,比如现在把avname的默认值设置成"欢迎光临" ,然后把avname的属性删除掉

    XiaojiejieItem.defaultProps = {
        avname:'欢迎光临'
    }
    

    (4.5.11) ref的使用

    在编写组件中的方法时,经常会遇到语义化很模糊的代码,这对于团队开发是一个很大的问题。因为review代码或者合作时都会影响开发效率。或者到这核心成员离开,项目倒闭的严重影响。所以我们必须重视react代码当中的语义化

    1. 代替原来项目中e.target.value
    以前的案例中,我们写了下面的代码,使用了e.target,这并不直观,也不好看。这种情况我们可以使用ref来进行解决

    之前代码:

    inputChange(e){
    
        this.setState({
            inputValue:e.target.value
        })
    }
    

    如果要使用ref来进行,需要现在JSX中进行绑定, 绑定时最好使用ES6语法中的箭头函数,这样可以简洁明了的绑定DOM元素。

    <input 
        id="jspang" 
        className="input" 
        value={this.state.inputValue} 
        onChange={this.inputChange.bind(this)}
        //关键代码——----------start
        ref={(input)=>{this.input=input}}
        //关键代码------------end
        />
    

    绑定后可以把上边的类改写成如下代码:

    inputChange(){
        this.setState({
            inputValue:this.input.value
        })
    }
    

    这就使我们的代码变得语义化和优雅的多。但是就我个人的经验来讲,我是不建议用ref这样操作的,因为React的是数据驱动的,所以用ref会出现各种问题。

    1. ref中的坑
    比如现在我们要用ref绑定取得要服务的数量,可以先用ref进行绑定。

    <ul ref={(ul)=>{this.ul=ul}}>
        {
            this.state.list.map((item,index)=>{
                return (
                    <XiaojiejieItem 
                    key={index+item}  
                    content={item}
                    index={index}
                    deleteItem={this.deleteItem.bind(this)}
                    />
                )
            })
        }
    </ul> 
    

    绑定后可以在addList()方法中,获取当前<div>的值.

     addList(){
        this.setState({
            list:[...this.state.list,this.state.inputValue],
            inputValue:''
        })
        //关键代码--------------start
        console.log(this.ul.querySelectorAll('div').length)
        //关键代码--------------end
    
    }
    

    这时候你打开控制台,点击添加服务按钮,你会返现数量怎么少一个?(就是这个坑),其实这个坑是因为React中更多setState是一个异步函数所造成的。也就是这个setState,代码执行是需要有一个时间的,如果你真的想了解清楚,你需要对什么是虚拟DOM有一个了解。简单的说,就是因为是异步,还没等虚拟Dom渲染,我们的console.log就已经执行了。

    那这个代码怎么编写才会完全正常那,其实setState方法提供了一个回调函数,也就是它的第二个函数。下面这样写就可以实现我们想要的方法了。

    addList(){
        this.setState({
            list:[...this.state.list,this.state.inputValue],
            inputValue:''
            //关键代码--------------start
        },()=>{
            console.log(this.ul.querySelectorAll('div').length)
        })
        //关键代码--------------end
    }
    

    (4.6) React 生命周期

    React的生命周期从广义上分为三个阶段:挂载、渲染、卸载

    因此可以把React的生命周期分为两类:挂载卸载过程和更新过程。

    (4.6.1) React的生命周期图

    (4.6.2) 废弃三个旧的生命周期函数

    React V16.3 版本中,为下面三个生命周期函数加上了 UNSAFE

    • UNSAFE_componentWillMount
    • UNSAFE_componentWillReceiveProps
    • UNSAFE_componentWillUpdate
    • 标题中的废弃不是指真的废弃,只是不建议继续使用,并表示在 V17.0 版本中正式删除。先来说说React为什么要这么做。

    主要是这些生命周期方法经常被_误用和滥用_。并且在 React V16.0 之前,React 是_同步渲染的_,而在 V16.0 之后 React 更新了其渲染机制,是通过_异步的方式进行渲染_的,在 render 函数之前的所有函数都有可能被执行多次。

    长期以来,原有的生命周期函数总是会诱惑开发者在 render 之前的生命周期函数中做一些动作,现在这些动作还放在这些函数中的话,有可能会被调用多次,这肯定不是我们想要的结果。

    (4.6.2.1)废弃 UNSAFE_componentWillMount 的原因

    有一个常见的问题,有人问为什么不在 UNSAFE_componentWillMount 中写 AJAX 获取数据的功能,他们的观点是,UNSAFE_componentWillMountrender 之前执行,早一点执行早得到结果。但是要知道,在 UNSAFE_componentWillMount 中发起 AJAX 请求,不管多快得到结果也赶不上首次 render,数据都是要在 render 后才能到达。

    而且 UNSAFE_componentWillMount 在服务器端渲染也会被调用到(此方法是服务端渲染唯一会调用的生命周期函数。),你肯定不希望AJAX 请求被执行多次,所以这样的IO 操作放在 componentDidMount 中更合适。

    尤其是在 Fiber 启用了异步渲染之后,更没有理由在 UNSAFE_componentWillMount 中进行 AJAX 请求了,因为 UNSAFE_componentWillMount 可能会被调用多次,谁也不会希望无谓地多次调用 AJAX 吧。

    还有人会将事件监听器(或订阅)添加到 UNSAFE_componentWillMount 中,但这可能导致服务器渲染(永远不会调用 componentWillUnmount)和异步渲染(在渲染完成之前可能被中断,导致不调用 componentWillUnmount)的内存泄漏。

    人们通常认为 UNSAFE_componentWillMountcomponentWillUnmount 是成对出现的,但这并不能保证。只有调用了 componentDidMount 之后,React 才能保证稍后调用 componentWillUnmount 进行清理。因此,添加监听器/订阅的推荐方法是使用 componentDidMount 生命周期。

    (4.6.2.2)废弃 UNSAFE_componentWillReceiveProps 的原因

    有时候组件在 props 发生变化时会产生副作用。与 UNSAFE_componentWillUpdate 类似,UNSAFE_componentWillReceiveProps 可能在一次更新中被多次调用。因此,避免在此方法中产生副作用非常重要。相反,应该使用 componentDidUpdate,因为它保证每次更新只调用一次。

    UNSAFE_componentWillReceiveProps 是考虑到因为父组件引发渲染可能要根据 props 更新 state 的需要而设立的。新的 getDerivedStateFromProps 实际上与 componentDidUpdate 一起取代了以前的 UNSAFE_componentWillReceiveProps 函数。

    (4.6.2.3)废弃 UNSAFE_componentWillUpdate 的原因

    有些人使用 UNSAFE_componentWillUpdate 是出于一种错误的担心,即当 componentDidUpdate 触发时,更新其他组件的 state 已经”太晚”了。事实并非如此。React 可确保在用户看到更新的UI之前,刷新在 componentDidMountcomponentDidUpdate 期间发生的任何 setState 调用。

    通常,最好避免这样的级联更新。当然在某些情况下,这些更新也是必需的(例如:如果你需要在测量渲染的 DOM 元素后,定位工具的提示)。不管怎样,在异步模式下使用 UNSAFE_componentWillUpdate 都是不安全的,因为外部回调可能会在一次更新中被多次调用。相反,应该使用 componentDidUpdate 生命周期,因为它保证每次更新只调用一次。

    大多数开发者使用 UNSAFE_componentWillUpdate 的场景是配合 componentDidUpdate,分别获取 rerender 前后的视图状态,进行必要的处理。但随着 React 新的 suspensetime slicing、异步渲染等机制的到来,render 过程可以被分割成多次完成,还可以被暂停甚至回溯,这导致 UNSAFE_componentWillUpdatecomponentDidUpdate 执行前后可能会间隔很长时间,足够使用户进行交互操作更改当前组件的状态,这样可能会导致难以追踪的 BUG

    React 新增的 getSnapshotBeforeUpdate 方法就是为了解决上述问题,因为 getSnapshotBeforeUpdate 方法是在 UNSAFE_componentWillUpdate 后(如果存在的话),在 React 真正更改 DOM 前调用的,它获取到组件状态信息更加可靠。

    除此之外,getSnapshotBeforeUpdate 还有一个十分明显的好处:它调用的结果会作为第三个参数传入 componentDidUpdate,避免了 UNSAFE_componentWillUpdatecomponentDidUpdate 配合使用时将组件临时的状态数据存在组件实例上浪费内存,getSnapshotBeforeUpdate 返回的数据在 componentDidUpdate 中用完即被销毁,效率更高。

    (4.6.3) 新增两个生命周期函数

    React V16.3 中在废弃(这里的废弃不是指真的废弃,只是不建议继续使用,并表示在 V17.0 版本中正式删除)三个旧的生命周期函数的同时,React 还新增了两个生命周期函数:

    • static getDerivedStateFromProps
    • getSnapshotBeforeUpdate
      React V16.3 版本中加入的 static getDerivedStateFromProps 生命周期函数存在一个问题,就是在生命周期的更新阶段只有在 props 发生变化的时候才会调用 static getDerivedStateFromProps,而在调用了 setStateforceUpdate 时则不会。

    React 官方也发现了这个问题,并在 React V16.4 版本中进行了修复。也就是说在更新阶段中,接收到新的 props,调用了 setStateforceUpdate 时都会调用 static getDerivedStateFromProps。具体在下面讲到这个函数的时候有详细说明。

    (4.6.4) React 生命周期梳理

    React 生命周期主要分为三个阶段:

    • 挂载阶段
    • 更新阶段
    • 卸载阶段
    (4.6.4.1) 挂载阶段

    挂载阶段也可以理解为初始化阶段,也就是把我们的组件插入到 DOM 中。这个阶段的过程如下:

    • constructor
    • getDerivedStateFromProps
    • UNSAVE_componentWillMount
    • render
    • (React Updates DOM and refs)
    • componentDidMount

    constructor
    组件的构造函数,第一个被执行。如果在组件中没有显示定义它,则会拥有一个默认的构造函数。如果我们显示定义构造函数,则必须在构造函数第一行执行 super(props),否则我们无法在构造函数里拿到 this,这些都属于 ES6 的知识。

    在构造函数中,我们一般会做两件事:

    • 初始化 state
    • 对自定义方法进行 this 的绑定
    constructor(props) {
        super(props);
    
        this.state = {
          width,
          height: 'atuo',
        }
    
        this.handleChange1 = this.handleChange1.bind(this);
        this.handleChange2 = this.handleChange2.bind(this);
    }
    

    getDerivedStateFromProps
    使用方式:

    //static getDerivedStateFromProps(nextProps, prevState)
    
    class Example extends React.Component {
      static getDerivedStateFromProps(props, state) {
        //根据 nextProps 和 prevState 计算出预期的状态改变,返回结果会被送给 setState
        // ...
      }
    }
    

    新的 getDerivedStateFromProps 是一个静态函数,所以不能在这函数里使用 this,简单来说就是一个纯函数。也表明了 React 团队想通过这种方式防止开发者滥用这个生命周期函数。每当父组件引发当前组件的渲染过程时,getDerivedStateFromProps 会被调用,这样我们有一个机会可以根据新的 props 和当前的state来调整新的 state

    这个函数会返回一个对象用来更新当前的 state,如果不需要更新可以返回 null。这个生命周期函数用得比较少,主要用于在重新渲染期间手动对滚动位置进行设置等场景中。该函数会在挂载时,在更新时接收到新的 props,调用了 setStateforceUpdate 时被调用。

    新的 getDerivedStateFromProps 实际上与 componentDidUpdate 一起取代了以前的 UNSAFE_componentWillReceiveProps 函数。UNSAFE_componentWillReceiveProps 也是考虑到因为父组件引发渲染可能要根据 props 更新 state 的需要而设立的。

    UNSAVE_componentWillMount
    UNSAFE_componentWillMount() 在挂载之前被调用。它在 render() 之前调用,因此在此方法中同步调用 setState() 不会触发额外渲染。通常,我们建议使用 constructor() 来初始化 state。避免在此方法中引入任何副作用或订阅。如遇此种情况,请改用 componentDidMount()

    此方法是服务端渲染唯一会调用的生命周期函数。UNSAFE_componentWillMount() 常用于当支持服务器渲染时,需要同步获取数据的场景。

    render
    这是 React 中最核心的方法,class 组件中唯一必须实现的方法。

    render 被调用时,它会检查 this.propsthis.state 的变化并返回以下类型之一:

    • 原生的 DOM,如 div
    • React 组件
    • 数组或 Fragment
    • Portals(插槽)
    • 字符串和数字,被渲染成文本节点
    • Booleannull,不会渲染任何东西
      render() 函数应该是一个纯函数,里面只做一件事,就是返回需要渲染的东西,不应该包含其它的业务逻辑,如数据请求,对于这些业务逻辑请移到 componentDidMountcomponentDidUpdate 中。

    componentDidMount
    componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。依赖于DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。这个方法是比较适合添加订阅的地方。如果添加了订阅,请不要忘记在 componentWillUnmount() 里取消订阅

    你可以在 componentDidMount() 里直接调用 setState()。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在render() 两次调用的情况下,用户也不会看到中间状态。

    请谨慎使用该模式,因为它会导致性能问题。通常,你应该在 constructor() 中初始化 state。如果你的渲染依赖于 DOM 节点的大小或位置,比如实现 modalstooltips 等情况下,你可以使用此方式处理

    (4.6.4.2) 更新阶段

    更新阶段是指当组件的 props 发生了改变,或组件内部调用了setState或者发生了 forceUpdate,则进行更新。

    这个阶段的过程如下:

    • UNSAFE_componentWillReceiveProps
    • getDerivedStateFromProps
    • shouldComponentUpdate
    • UNSAFE_componentWillUpdate
    • render
    • getSnapshotBeforeUpdate
    • (React Updates DOM and refs)
    • componentDidUpdate

    UNSAFE_componentWillReceiveProps
    UNSAFE_componentWillReceiveProps 是考虑到因为父组件引发渲染可能要根据 props 更新 state 的需要而设立的。UNSAFE_componentWillReceiveProps 会在已挂载的组件接收新的 props 之前被调用。如果你需要更新状态以响应 prop 更改(例如,重置它),你可以比较 this.propsnextProps 并在此方法中使用 this.setState() 执行 state 转换。

    如果父组件导致组件重新渲染,即使 props 没有更改,也会调用此方法。如果只想处理更改,请确保进行当前值与变更值的比较。在挂载过程中,React 不会针对初始 props 调用UNSAFE_componentWillReceiveProps()。组件只会在组件的 props 更新时调用此方法。调用 this.setState() 通常不会触发 UNSAFE_componentWillReceiveProps()

    getDerivedStateFromProps
    这个方法在挂载阶段已经讲过了,这里不再赘述。记住该函数会在挂载时,在更新时接收到新的 props,调用了 setStateforceUpdate 时被调用。它与componentDidUpdate一起取代了以前的 UNSAFE_componentWillReceiveProps 函数。

    shouldComponentUpdate

    shouldComponentUpdate(nextProps, nextState) {
      //...
    }
    

    它有两个参数,根据此函数的返回值来判断是否进行重新渲染,true 表示重新渲染,false 表示不重新渲染,默认返回 true。注意,首次渲染或者当我们调用 forceUpdate 时并不会触发此方法。此方法仅用于性能优化。

    因为默认是返回true,也就是只要接收到新的属性和调用了 setState 都会触发重新的渲染,这会带来一定的性能问题,所以我们需要将this.propsnextProps 以及 this.statenextState 进行比较来决定是否返回 false,来减少重新渲染,以优化性能。请注意,返回false 并不会阻止子组件在 state 更改时重新渲染。

    但是官方提倡我们使用内置的 PureComponent 来减少重新渲染的次数,而不是手动编写 shouldComponentUpdate 代码。PureComponent 内部实现了对 propsstate 进行浅层比较。

    如果 shouldComponentUpdate() 返回 false,则不会调用 UNSAFE_componentWillUpdate()render()componentDidUpdate()。官方说在后续版本,React 可能会将 shouldComponentUpdate 视为提示而不是严格的指令,并且,当返回 false 时,仍可能导致组件重新渲染。

    UNSAFE_componentWillUpdate
    当组件收到新的 propsstate 时,会在渲染之前调用 UNSAFE_componentWillUpdate()。使用此作为在更新发生之前执行准备更新的机会。初始渲染不会调用此方法。但是你不能此方法中调用 this.setState()。在 UNSAFE_componentWillUpdate() 返回之前,你也不应该执行任何其他操作(例如,dispatch Reduxaction)触发对 React 组件的更新。

    通常,此方法可以替换为 componentDidUpdate()。如果你在此方法中读取 DOM 信息(例如,为了保存滚动位置),则可以将此逻辑移至 getSnapshotBeforeUpdate() 中。

    render
    这个方法在挂载阶段已经讲过了,这里不再赘述。

    getSnapshotBeforeUpdate

    getSnapshotBeforeUpdate(prevProps, prevState) {
      //...
    }
    

    getSnapshotBeforeUpdate 生命周期方法在 render 之后,在更新之前(如:更新 DOM 之前)被调用。给了一个机会去获取 DOM 信息,计算得到并返回一个 snapshot,这个 snapshot 会作为 componentDidUpdate 的第三个参数传入。如果你不想要返回值,请返回 null,不写的话控制台会有警告。

    并且,这个方法一定要和 componentDidUpdate 一起使用,否则控制台也会有警告。getSnapshotBeforeUpdatecomponentDidUpdate 一起,这个新的生命周期涵盖过时的 UNSAFE_componentWillUpdate 的所有用例。

    getSnapshotBeforeUpdate(prevProps, prevState) {
      console.log('#enter getSnapshotBeforeUpdate');
      return 'foo';
    }
    
    componentDidUpdate(prevProps, prevState, snapshot) {
      console.log('#enter componentDidUpdate snapshot = ', snapshot);
    }
    

    上面这段代码可以看出来这个 snapshot 怎么个用法,snapshot 乍一看还以为是组件级别的某个“快照”,其实可以是任何值,到底怎么用完全看开发者自己,getSnapshotBeforeUpdatesnapshot 返回,然后 DOM 改变,然后 snapshot 传递给 componentDidUpdate

    官方给了一个例子,用 getSnapshotBeforeUpdate 来处理 scroll,并且说明了通常不需要这个函数,只有在重新渲染过程中手动保留滚动位置等情况下非常有用,所以大部分开发者都用不上,也就不要乱用

    componentDidUpdate

    componentDidUpdate(prevProps, prevState, snapshot) {
      //...
    }
    

    componentDidUpdate() 会在更新后会被立即调用。首次渲染不会执行此方法。在这个函数里我们可以操作 DOM,和发起服务器请求,还可以 setState,但是注意一定要用if 语句控制,否则会导致无限循环。

    componentDidUpdate(prevProps) {
      // 典型用法(不要忘记比较 props):
      if (this.props.userID !== prevProps.userID) {
        this.fetchData(this.props.userID);
      }
    }
    

    如果组件实现了 getSnapshotBeforeUpdate() 生命周期,则它的返回值将作为 componentDidUpdate() 的第三个参数 snapshot 参数传递。否则此参数将为 undefined

    (4.6.4.3) 卸载阶段

    卸载阶段,这个阶段的生命周期函数只有一个:

    componentWillUnmount
    componentWillUnmount() 会在组件卸载及销毁之前直接调用。我们可以在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在componentDidMount()中创建的订阅等。注意不要在这个函数里调用setState(),因为组件不会重新渲染了。

    static getDerivedStateFromError()

    static getDerivedStateFromError(error) {
      //...
    }
    

    此生命周期会在后代组件抛出错误后被调用。它将抛出的错误作为参数,并返回一个值以更新 stategetDerivedStateFromError() 会在渲染阶段调用,因此不允许出现副作用。如遇此类情况,请改用 componentDidCatch()

    componentDidCatch()

    componentDidCatch(error, info) {
      //...
    }
    

    此生命周期在后代组件抛出错误后被调用。它接收两个参数:

    1. error —— 抛出的错误。
    2. info —— 带有componentStack key 的对象,其中包含有关组件引发错误的栈信息。
      componentDidCatch() 会在“提交”阶段被调用,因此允许执行副作用。它应该用于记录错误之类的情况:

    如果发生错误,你可以通过调用 setState 使用 componentDidCatch() 渲染降级 UI,但在未来的版本中将不推荐这样做。可以使用静态getDerivedStateFromError() 来处理降级渲染。

    (4.7) React-请求远程数据

    ajax请求框架Axios请求远程数据

    1. 安装
    Axios的安装可以使用npm来进行安装,你可以直接在项目根目录下,输入下面的代码。

    npm install -save axios
    

    2. axios请求远程数据
    安装好axiso之后,需要在使用ajax的地方先引入axios,比如现在想在Xiaojiejie.js中使用axios,写入下面的代码进行引入:

    import axios from 'axios'
    

    引入后,可以在componentDidMount生命周期函数里请求ajax,我也建议在componentDidMount函数里执行,因为在render里执行,会出现很多问题,比如一直循环渲染;在componentWillMount里执行,在使用RN时,又会有冲突。所以强烈建议在componentDidMount函数里作ajax请求。

    componentDidMount(){
        axios.post('https://web-api.juejin.im/v3/web/wbbr/bgeda')
            .then((res)=>{console.log('axios 获取数据成功:'+JSON.stringify(res))  })
            .catch((error)=>{console.log('axios 获取数据失败'+error)})
    }
    

    上面的代码是以掘金的一个接口为例,做了一次ajax请求。并且请求到了数据,给我们返回了。

    (4.8) React-Mock数据

    mock数据网站:

    https://www.fastmock.site/#/projects

    创建项目,创建接口,添加数据

    将远程获得的数据赋值给数组:

    
    componentDidMount(){
        axios.get('xxxx')
            .then((res)=>{
                console.log('axios 获取数据成功:'+JSON.stringify(res))
    
                this.setState({
                    list:res.data.data
                })
              })
            .catch((error)=>{console.log('axios 获取数据失败'+error)})
    }
    

    (4.9) 实现react动画

    (4.9.1) 用css3实现react动画

    1. 新建组件Boss
    需要给“小姐姐服务菜单”增加一个Boss服务人物,点击一下按钮就会自动出现"Boss级人物-孙悟空",不要管什么恰当不恰当了,咱们是为了练习一下动画。在src文件夹下,新建一个Boss.js文件

    import React, { Component } from 'react';
    class Boss extends Component {
        constructor(props) {
            super(props);
            this.state = {  }
        }
        render() { 
            return ( 
                <div>
                    <div>BOSS级人物-孙悟空</div>
                    <div><button>召唤Boss</button></div>
                </div>
              );
        }
    }
    
    export default Boss;
    

    2. 业务逻辑

    目前组件没有任何业务逻辑,只有一个UI,这是没办法实现动画效果的。业务逻辑是点击按钮的时候可以改变字的''显示隐藏。

    要实现这个业务逻辑,先在constructor里增加stateisShow,详情请看下面的代码。

    this.state = { 
        isShow:true
    }
    

    然后把“字”的部分,增加className,并用isShow进行控制。

    <div className={this.state.isShow ? 'show' : 'hide'}>BOSS级人物-孙悟空</div>
    

    需要点击按钮时,有响应的事件,所以需要一个方法,我们编写一个toToggole()方法

    toToggole(){
        this.setState({
            isShow:this.state.isShow ? false : true
        })
    }
    

    意思就是当isShowtrue时,我们赋值false;当isShowfalse时,我们赋值true

    有了方法后,可以给<button>加上onClick响应事件了

    <div><button onClick={this.toToggole}>召唤Boss</button></div>
    

    写完这个事件,还是需要到constructor里绑定一下this

    constructor(props) {
        super(props);
        this.state = { 
            isShow:true
        }
        this.toToggole = this.toToggole.bind(this);
    }
    

    这样我们的基本业务逻辑就算写完了,可以把代码加入到Xiaojiejie组件中,看一下效果了。

    3. 加入CSS动画
    在页面上看不出任何的效果,如果你打开浏览器控制台是可以看到每次点击按钮,class都会变化的。界面没变化,知识我们没有写CSS。现在可以在style.css里写样式,代码如下:

    .show{ opacity: 1; transition:all 1.5s ease-in;}
    .hide{opacity: 0; transition:all 1.5s ease-in;}
    

    这样就用CSS3实现了React中动画,这知识最简单的实践动画

    (4.9.2) css3的Keyframes动画

    transition只能作一些最简单的动画,如果你想稍微复杂点,transition就做不出来了。这时候就可以用CSS3中的关键帧动画keyframes

    ** 1. keyframes介绍**
    此属性与animation属性是密切相关的,keyframes译成中文就是关键帧,最早接触这个关键帧的概念是字flash中,现在Flash已经退出历史舞台了。他和transition比的优势是它可以更加细化的定义动画效果。比如我们之前的按钮隐藏动画,不仅可以设置透明度,还可以设置颜色。

    @keyframes hide-item{
        0% {
            opacity:1;
            color:yellow;
        }
        50%{
            opacity: 0.5 ;
            color:red;
        }
        100%{
            opacity:0;
            color: green;
        }
    }
    

    2.使用动画
    使用动画的关键词是animation,然后后边跟上你的制作的动画名称,如下面这段代码。

    .hide{ animation:hide-item 2s ease-in ; }
    

    这句的意思就是,使用hide-item动画,持续时间是2秒钟,然后缓动效果是由慢到快(开始的时候慢,之后快)。

    但是你会发现,动画执行一遍后又恢复了原状,这个是因为没设置forwards属性,它是用来控制停止到最后一帧的。 我们把代码改写成下面的样子。

    .hide{ animation:hide-item 2s ease-in forwards; }
    

    完整代码css:

    .show{ animation:show-item 2s ease-in forwards; }
    .hide{ animation:hide-item 2s ease-in forwards; }
    
    @keyframes hide-item{
        0% {
            opacity:1;
            color:yellow;
        }
        50%{
            opacity: 0.5 ;
            color:red;
        }
        100%{
            opacity:0;
            color: green;
        }
    }
    
    @keyframes show-item{
        0% {
            opacity:0;
            color:yellow;
        }
        50%{
            opacity: 0.5 ;
            color:red;
        }
        100%{
            opacity:1;
            color: green;
        }
    }
    

    keyframes也是只能实现很简单的动画效果,一些复杂的动画最好还是使用别人造好的轮子,下节课继续学习React中的动画吧。

    (4.9.3) react-transition-group

    React生态中有很多第三方的动画组件,你应该学习一下react-transition-group动画组件

    1. 安装
    使用它要先进行安装,这里使用npm的形式进行安装了,当然也可以使用yarn

    先用VSCode打开项目根目录,然后打开终端,输入下面的命令,进行安装:

    npm install react-transition-group --save
    

    安装好后,你可以先去github上来看一下文档,他是有着三个核心库(或者叫组件)。

    • Transition

    • CSSTransition

    • TransitionGroup

    2. 使用CSSTransition

    其实这个库用起来根ng-animate差不多,先来看看如何使用CSSTransition

    先用import进行引入,代码如下:

    import { CSSTransition } from 'react-transition-group'
    

    引入后便可以使用了,使用的方法就和使用自定义组件一样,直接写<CSSTransition>,而且不再需要管理className了,这部分由CSSTransition进行管理。修改上节课写的Boss.js文件里的render区域。

    render() { 
        return ( 
            <div>
                <CSSTransition 
                    in={this.state.isShow}   //用于判断是否出现的状态
                    timeout={2000}           //动画持续时间
                    classNames="boss-text"   //className值,防止重复
                >
                    <div>BOSS级人物-孙悟空</div>
                </CSSTransition>
                <div><button onClick={this.toToggole}>召唤Boss</button></div>
            </div>
            );
    }
    

    需要注意的是classNames这个属性是由s的,如果你忘记写,会和原来的ClassName混淆出错,这个一定要注意。

    然后你就可以到CSS中改写style了。在修改样式之前,有那些类名。

    • xxx-enter: 进入(入场)前的CSS样式;
    • xxx-enter-active:进入动画直到完成时之前的CSS样式;
    • xxx-enter-done:进入完成时的CSS样式;
    • xxx-exit:退出(出场)前的CSS样式;
    • xxx-exit-active:退出动画知道完成时之前的的CSS样式。
    • xxx-exit-done:退出完成时的CSS样式。

    知道了这些要设置的CSS,就可以删除原来写的CSS了,把下面的代码写上:

    .input {border:3px solid #ae7000}
    
    .boss-text-enter{
        opacity: 0;
    }
    .boss-text-enter-active{
        opacity: 1;
        transition: opacity 2000ms;
    
    }
    .boss-text-enter-done{
        opacity: 1;
    }
    .boss-text-exit{
        opacity: 1;
    }
    .boss-text-exit-active{
        opacity: 0;
        transition: opacity 2000ms;
    
    }
    .boss-text-exit-done{
        opacity: 0;
    }
    

    这时候你的动画样式就正常了,你回发现我们再也不用自己管理className了,而是完全交给了react-transition-group来作。

    3. unmountOnExit属性
    我们给<CSSTransition>加上unmountOnExit,加上这个的意思是在元素退场时,自动把DOM也删除,这是以前用CSS动画没办法做到的。

    render() { 
        return ( 
            <div>
                <CSSTransition 
                    in={this.state.isShow}   //用于判断是否出现的状态
                    timeout={2000}           //动画持续时间
                    classNames="boss-text"   //className值,防止重复
                    unmountOnExit
                >
                    <div>BOSS级人物-孙悟空</div>
                </CSSTransition>
                <div><button onClick={this.toToggole}>召唤Boss</button></div>
            </div>
            );
    }
    

    (4.9.4) 多DOM动画制作和编写

    控制多个动画react-transition-group这个动画库也是可以做到的。

    1. 使用TransitionGrop

    它就是负责多个DOM元素的动画的,我们还是拿小姐姐这个案例作例子,现在可以添加任何的服务项目,但是都是直接出现的,没有任何动画,现在就给它添加上动画。添加动画,先引入transitionGrop

    直接打开/src/Xiaojiejie.js的文件,然后在最顶部同时

    import {CSSTransition , TransitionGroup} from 'react-transition-group'
    

    引入之后,就可以使用这个组件了,方法是在外层增加<TransitionGroup>标签。

    <ul ref={(ul)=>{this.ul=ul}}>
        <TransitionGroup>
        {
            this.state.list.map((item,index)=>{
                return (
                    <XiaojiejieItem 
                    key={index+item}  
                    content={item}
                    index={index}
                    deleteItem={this.deleteItem.bind(this)}
                    />
                )
            })
        }
        </TransitionGroup>
    </ul>
    

    这个需要放在循环的外边,这样才能形成一个组动画,但是只有这个<TransitonGroup>是不够的,你还是需要加入<CSSTransition>,来定义动画。

    2. 加入标签

    可以完全仿照上节课的经验,为Xiaojiejie组件,加上具体的动画设置,就可以实现多DOM元素的动画效果了。代码如下:

    <ul ref={(ul)=>{this.ul=ul}}>
        <TransitionGroup>
        {
            this.state.list.map((item,index)=>{
                return (
                    <CSSTransition
                        timeout={1000}
                        classNames='boss-text'
                        unmountOnExit
                        appear={true}
                        key={index+item}  
                    >
                        <XiaojiejieItem 
                        content={item}
                        index={index}
                        deleteItem={this.deleteItem.bind(this)}
                        />
                    </CSSTransition>
                )
            })
        }
        </TransitionGroup>
    </ul>  
    <Boss />
    </Fragment>
    

    总结:React动画还有很多知识,能做出很多酷炫的效果,完全可以单独分出来一个岗位,我在工作中用的都是比较简单的动画,用react-transition-group动画已经完全可以满足我的日常开发需求了。如果你想学习更多的React动画知识,可以看看文档或者书。

  • 相关阅读:
    命名规范
    操作文件和目录
    使用本地shadow socks代理
    发送邮件
    sql参数化
    定义常量
    获取嵌套字典值的方法
    通过字符串调用函数
    用字典优化过长的if 语句
    操作文件和目录
  • 原文地址:https://www.cnblogs.com/MrYuChen-Blog/p/14550361.html
Copyright © 2020-2023  润新知