web前端学习(二):React是最优秀的web框架,基于JS专注于视图层

React是用于构建用户界面的JavaScript库, 只关注于视图,主要表现为将数据渲染为HTML视图。

一、React 基础学习

npx普通安装

创建一个项目
npx create-react-app react-app          创建一个react-app项目 
yarn start                              启动项目(默认3000端口)
http://localhost:3000/               

高级安装

--使用脚手架
npm i -g create-react-app

create-react-app xxx                    创建新的项目
yarn start                              启动项目(默认3000端口)

注意:React创建项目,不能大写字母开头。 它不像Laravel 一样,项目名称可以自定义

项目名称使用 连字符号 - , 而不是使用 驼峰命名法

1.react与原生JS

前端开发流程

  1. 发送请求获取数据

  2. 处理数据

  3. 操作DOM呈现页面 React 只负责第3步

使用原生js的缺点
1.原生JavaScript操作DOM繁琐、效率低(DOM-API操作UI)。

2.使用JavaScript直接操作DOM,浏览器会进行大量的重绘重排。

3.原生JavaScript没有组件化编码方案,代码复用率低。

React的特点:
1.使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互。

2.采用组件化模式、声明式编码,提高开发效率及组件复用率。

3. 在React Native中可以使用React语法进行移动端开发。

React的原理:

  1. React先根据数据创建虚拟Dom,再把虚拟Dom变为真实的Dom
  2. 当有数据增加时,先对比虚拟Dom是否有改变。没有改变的直接复用,有改变的进行调整。
  3. 比如渲染了100条,又增加了1条,对比前100条的虚拟Dom没有变化,直接复用,只需要重新渲染新增加的一条即可

2.diffing算法

diffing算法中,最关键的是Key值

1.虚拟DOM中key的作用:

  1. 简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。

  2. 详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DON],
    随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:

    • 旧虚拟DOM中找到了〔新虚拟DOM相同的key:
      (1).若虚拟DOM中内容没变,直接使用之前的真实DOM
      (2).若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实

    • 旧虚拟DOM中未找到与新虚拟DOM相同的key
      根据数据创建新的真实DOM,随后渲染到到页面

2.用index作为列表的key可能会引发的问题:

  1. 若对 数据进行:逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新==>界面效果没问题,但效率低。

  2. 如果结构中还包含输入类的DOM, 会产生错误DOM更新==>界面有问题。

  3. 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表数据,用index是没问题的

  4. 使用唯一字段id和index的区别

    • 更新后数据虚拟DOM:
      <div key=3>小张--- 18</ div>     (转成真实DOM)
      <div key=1>小李--- 18</ div>     (不做更改)
      <div key=2>小王--- 19</div>      (不做更改)
      
    • 更新后数据虚拟DOM:
      <div key=0>小张—--18</ div>      (转成真实DOM)
      <div key=1>小李---18</ div>      (转成真实DOM)
      <div key=2>小王--- 19</div>      (转成真实DOM)
      
      

3.开发中选择key

  1. 最好使用每条数据的唯一标识作为key,如果id,手机 号,等等
  2. 如果后端的接口没有返回对应的标识字段,可以使用nanoid 模块,获取随机的唯一id值

4.关于虚拟DOM:

  1. 本质是0bject类型的对象(一般对象),虚拟DOM是一个对象,而真实DOM是一个DOM节点

  2. 虚拟DOM比较’轻’,真实DOM比较’重",因为虚拟DOM是React内部使用的,无需真实DOM上那么多属性

  3. 虚拟DOM最终会被React转化为真实D0M,呈现在页面

3.React事件处理

组件维护状态,状态存储数据,数据驱动页面

通过状态改变数据,以数据驱动页面的更新 ,react会根据数据的变化 自动更新页面

  1. 通过onXxx属性指定事件处理函数(注意大小写)

    (1. React使用的是自定义(合成)事件,而不是使用的原生DOM事件 — 为了更好的兼容性

    (2. React中的事件是通过"事件委托方式" 处理的(委托给组件最外层的div元素 — 为了更高效

  2. 通过event.target得到发生事件的DOM元素对象

   // 函数嵌套函数就会形成闭包。  所以说需要去改变this指向
   // 将inputChange里面的this指向当前的this. 否则形成闭包的函数中不能获取this.
   <input type="text" value={this.state.inputValue} onChange={this.inputChange.bind(this)}/>
   
   
    //  通过bind绑定当前的this
    // 这个事件是由input标签触发的,所以它的this指向input对象
     // 这会触发一个事件,默认是target,   数据是e.target.value  
    inputChange(e){     
        this.setState({        // 改变状态, 赋值无效
            inputValue:e.target.value
        }) 
    }
    
    // 通过箭头函数实现this的上级查找
     // this会从上一级中找 
    inputChange = ()=>{     
        this.setState({        // 改变状态, 赋值无效
            inputValue:e.target.value
        }) 
    }
   

4.React模板语法

1.基本语法

  1. jsx语法,不要写引号,类似XML文件的格式,返回的是标签。

  2. 对象格式的内联样式:多个节点,使用变量{title},使用内联样式style={ {color:‘red’, fontSize:‘50px’} }

  3. 类名变成className :而不是class

  4. 样式的驼峰习惯:font-size 改成fontSize, 最外面的{}表示的是里面数据是js语法,类似于一个json对象的格式""

  5. 内联事件的驼峰习惯:把原来的onclick改成 onClick, onClick = {demo}, 注意:有括号表示自动运行函数,而一般采用无括号或者回调函数的格式绑定事件。

2.运行实例

1.使用react的模板语法
    <div id="test">

    </div>
     <!--引入react核心库-->
     <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
     <!--引入react-dom库,用于支持react操作Dom-->
     <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
     <!--引入babel,用于将jsx转为js-->
     <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <!--引入babel,用于将jsx转为js-->
    <!--一定要写text/babel -->
    <script type="text/babel">
        let id = "title";
        let content = "Hello";

        // 1。创建虚拟DOM
        const VDOM = <h1>Hello</h1>;                             // 1.单节点
        const VDOM = <h1 id="title"><span>Hello</span></h1>;     // 2.多个节点
        const VDOM = (
             <h1 id="title">                                     // 3.多个节点,缩进的格式
                 <span>Hello</span>
             </h1>
        );

        const VDOM = (
             <h1 id={title} className="myTitle">
                 <span  style={ {color:'red', fontSize:'50px'} }>{Hello}</span>
             </h1>
        );
        // 2.渲染虚拟DOM到页面
        ReactDOM.render( VDOM,document.getElementById( 'test'))
    </script>


2.两种创建虚拟DOM的方式(jsx , js 两种方式)
    <!--准备一个窗口-->
    <div id="test">

    </div>
    <!--引入react核心库-->
    <script src="./js/react.development.js"></script>
    <!--引入react-dom库,用于支持react操作Dom-->
    <script src="./js/react-dom.development.js"></script>
    <script >
        // 1。创建虚拟DOM
        const VDOM = React.createElement('h1',{id:'title'},'Hello');

        // 再添加子节点
        const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello'));
        // 2.渲染虚拟DOM到页面
        ReactDOM.render( VDOM,document.getElementById( 'test'))
    </script>

5.jsx 与模块化

(1. 什么是jsx?

1.基本概念

  1. 全称:JavaScript XML

  2. react定义的一种类似于XML的JS扩展语法: js+XML本质是React.createElement (component,props …children)方法的语法糖

  3. 作用:用来简化创建虚拟DOM

    (1.写法: var ele = <h1>Hello Jsx!</h1>

    (2.注意1:它不是字符串,也不是HTML/XML标签

    (3.注意2∶它最终产生的就是一个js对象

  4. 基本语法规则
    (1.遇到<开头的代码,以标签的语法解析: html同名标签转换为html同名元素,其它标签需要特别解析

    (2.遇到以{开头的代码,以js语法解析:标签中的js表达式必须用{ }包含,即引入变量的时候

  5. babel.js的作用
    (1. 浏览器不能直接解析JSx代码,需要babel转译为纯s的代码才能运行

    (2. 只要用了jsx,都要加上type=“text/babel”,声明需要babel来处理

2.什么是XML

(1.XML早期用于存储和传输数据

      <student>
           <name>LIli></name>
           <age>17</age>
      </student>

(2. JSON是目前更流行的的存储和传输数据的方式
(1.JSON.stringify可以将json格式 转换成字符串, 串行化
(2.JSON.parse 可以将字符串转换成原来的json对象,反串行化

3.jsx语法规则
1.定义虚拟DOM时,不要写引号
2.标签中混入JS表达式时,要用{}
3.样式的类名不要用class属性,要使用className属性
4.内联样式要用style={{key: value}}的形式去写
5.只能有一个根标签,多个标签会出错
6.标签必须闭合,单标签可以使用/来闭合
7.标签首字母
a.如果标签首字母是小写,会把该标签转为html中同名元素,如果找不到同名的元素,就会报错
b.如果标签首字母是大写,react就去渲染对应的组件,如果组件没有定义,就会报错

4.实例小案例
一定要注意区分:js语句 和 js表达式
表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
语句:if,for,switch逻辑结构

1.表达式
    a  + b
    demo(1)
    arr.map()
    function a (){ }
    三目运行符

2.语句
    for(){ }
    if(){ }
    switch(){ } 


​ 3.js中常用map, 和 三目运算符号 返回数据

(2.组件与模块学习
 1. 模块
     1.理解:向外提供特定功能的js程序,一般就是一个js文件
     2.为什么要拆成模块︰随着业务逻辑增加,代码越来越多且复杂。
     3.作用:复用js,简化js的编写,提高js运行效率
  1. 组件
    1.理解:用来实现局部功能效果的代码和资源的集合(html/cssjs/image等等)
    2.为什么要用组件:一个界面的功能更复杂
    3.作用:复用编码,简化项目编码,提高运行效率

  2. 模块化
    当应用的js都以模块来编写的,这个应用就是一个模块化的应用

  3. 组件化
    当应用是以多组件的方式实现,这个应用就是一个组件化的应用

6.类式与函数式组件

(1.函数式组件
  <script type="text/babel">
      //1.创建函数式组件
      function Demo( ) {
              console.log(this)// this是 undefined,因为 babel在翻译的时候,开启了严格模式
              return <h2>我是用函数定义的组件,适用于简单组件的定义</h2>
      }
      // 2、渲染组件到页面
      ReactDOM.render(<Demo />, document. getElementById( 'test ' ))
      /**
           执行了ReactDOM.render (<MyComponent />,...之后,发生了什么
           1. React解析组件标签,找到MyComponent这个组件
           2,发现组件是使用函数定义的,随后调用该函数,将函数返回的虚拟DOM转为真实的DON,随后呈现在页面中
      **/
  </script>
(2.类式组件

(1.类的基本知识

  1. 类中的构造方注不是必须写的,要对实例进行一些初始化的操作才写
  2. 如果A类继承B类,且A类中写了构造器,A类构造器必须写super()方法
  3. 类中定义的方法,都是放在了类的原型对象上,供实例去使用
  4. 在继承中,如果子类使用的方法没有在在子类原型对象上找到,就会通过原型链继续向父级查找
class Person {
        //构造方法
        constructor(name, ad) {
            // 没有对应的字段,就自动创建属性,并赋值
            this.name = name
            this.age = age
        }

        //类中可以直接写赋值赋值语句(不用关键字),
        //下面代码的含义是:给Car的实例对象添加一个属性,名为wheel,值是4
        wheel= 4

        //一般方法
        speak(){
            // speak方法在类的原型对象上,供实例调用
            //通过Person实例调用speak时,speak中的this就是Person实例
            console.log(`我的名字是:${this.name},我的年龄是;${this.age}`)
        }
}

//创建一个Student类,继承Person类
class Student extends Person {
        constructor(name,age, grade) {
            // super()   //子类重写父类的构造方法,一定要使用super()
            // this.name = name
            // this.age = age
            super(name,age);
            this.grade = grade
        }
        //重写父类的speak方法
        speak() {
            console.log(`我的名字是:${this.name},我的年龄是:${this.age},我的成绩是:${this.grade}`);
        }

}
let student = new Student( 'Bob ",20);
student.speak()

      

(2.类式组件的使用

//创建一个Person类
<script type="text/babel">
    //1,创健类式组件
     class MyComponent extends React.Component{
         render() {
             //render 放在MyComponent类的原型对象上,供实例调用
             //render中的this是MyComponent的实例对象
             console.log( render中的ttis是; ',this)// MyComponent {}
             return <h2>我是用类定义的组件,适用于复杂组件的定义</h2>   //state
        }
     // 2,将组件渲染到页面
      ReactDOM.render(<MyComponent />, document.getElementById( 'test ' ))
        /**
         执行了ReactDOM.render ( <MyComponent />,..、之后,发生了什么
        1.React解析组件标签,找到MyComponent这个组件
        2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用原型上的render方法,
        将render方法返回的的虚拟DOM转为真实的DOM,随后星现在页面中
        **/
        // 组件通过状态来驱动数据的更新,将虚拟DOM转换成真实DOM ,渲染成页面
</script>

(3.类中的this指向

(1.不同的this
      组件的状态驱动页面的改变
      //speak方法在类的原型对象上,供实例调用
      //通过Person实例调用speak时,speak中的this就是Person实例
      let p = new Person('Tom'.,18);
      p.speak();

      //把实例中的方法同值给一个变量
      // 通过变量调用的方法,this 是undefined
      // 其实,在类中,方法会默认开始局部的严格模式,所以this是undefined
      let x = p.speak;     x();

      // 在script标签中,调用全局的函数,this就指向window对象
      function demo(){
          console.log(this);
      }
      demo();

(2.改变this指向
      let x = demo.bind({a:1,b:2})
      x() ;              将demo函数中的this指向了新的对象, demo想要绑定新对象,demo就要绑定哪个对象的this

(3.//使用赋值语句,可以将方法添加到实例本身,而不是类的原型上面
        //自定义的方法--要用赋值语句+箭头函数,箭头函数没有this,会向上去同级寻找this
      changeWeather =()=> {
        let isHot = this.state.isHot
        this.setState({isHot,!isHot})
      }

(4.类式组件与函数式组件的区别

  1. 类式组件需要继承 React.Component组件,并且需要把返回的页面数据放在 **render(){}**方法中

  2. 类式组件则不需要以上操作, 但它们都需要使用 return (标签) 返回页面数据

7.ref 属性绑定标签

(1.字符串形式的ref

注意: 因为这种方式会存在问题,也一直没有解决这个问题,所以在未来的版本可能会移除这种绑定方法。值得一提的是,vue 中就采用这种字符串的方式进行绑定的。

 showData = ()=>{
     let {input1} = this.refs;              把refs的数据解构出来
     let {input1:value} = this.refs;        把refs的数据解构出来
     let input1 = this.refs.input1;         把refs的数据解构出来
     alert(input1.value);
 };
 <input ref="input1" type="text" placeholder="点击按钮显示数据"/>

(2.回调函数的ref

什么是回调函数?

你定义的函数, 你没有调用, 函数最终执行了



// 1.内联回调函数
showData = ()=>{
    let {input1} = this;            // 数据从this中获取
    let input1 = this.input1;       // 数据从this中获取
    alert(input1.value);
};
<input ref={c=> this.input1 = c} type="text" placeholder="点击按钮显示数据"/>


// 2.绑定类内定义的函数作为回调函数
saveinput = (c)=>{         
    this.input1 = c;      
};
<input ref={this.saveinput} type="text" placeholder="点击按钮显示数据"/>

1.两种方式的区别

  1. 绑定类内定义的函数的方式, 只是不会删除回调函数实例,

  2. 而内联函数的方式每次都会在渲染时创建一个新的函数实例。

2.回调ref具体运行了几次。

  1. 如果ref回调函数是以”内联函数的方式“定义的,在更新过程中它会被执行两次,第一次传入参数null,然后第二次会传入参数DOM元素。这是因为在每次渲染时会创建一个新的函数实例,所以React清空旧的ref并且设置新的。
  2. 通过将ref 的回调函数定义成”绑定类内定义函数的方式“可以避免上述问题。加载的时候可能会更缓慢一点,但是大多数情况下它是无关紧要的。
(3.hooks的createRef

注意: this.myRef.current , e.target. 和 ref绑定的 this.input 都是对应的DOM标签

import React,{useState,useEffect,useRef} from 'react';

myRef = React.createRef();
showData = ()=>{
    //  这个容器是专人专用,每个ref都要使用单独的一个
    console.log(this.myRef.current.value);    
};
<input ref={this.myRef} type="text" placeholder="点击按钮显示数据"/>

8.非受控组件与受控组件

(1.受控组件(事件)

(1.基本概念

onChange={this.saveUsername}

this.setState({password:event.target.value});

  • 随着你的输入维护状态,表单数据由状态维护state.
  • 推荐使用受控组件,不用ref,因为官方不让过度使用ref
  • 采用事件触发的方式 , 使用类内定义的函数,并采用state维护数据。
 
 class Login extends React.Component{
    // 初始化数据
    state = {
        username:"",
        password:"",
    };
    handleSubmit = (event)=>{
        event.preventDefault();                   // 阻止默认行为
        let {username,password} = this.state;     // 从state中获取数据
        alert(username+password);
    };

    // 保存输入的用户名
    saveUsername = (event) =>{
        this.setState({username:event.target.value});
    };
    // 保存输入的密码
    savePassword = (event) =>{
        this.setState({password:event.target.value});
    };

    render(){
        return ( 
            <form onSubmit={this.handleSubmit}>
                用户名:<input onChange={this.saveUsername}  type="text" /><br />
                密码:<input  onChange={this.savePassword}  type="password" /><br />
                <button>登录</ button>
            </form> 
            )
    }
}
(2.非受控组件(ref)

ref={event=>this.input=event}

  • 表单数据将交由DOM节点处理。表单的数据现用现取,从ref绑定DOM节点中获取表单数据
  • 采用回调函数的ref, 并且是内联回调函数式Ref, 让数据更语义化,也代替了e.target
  • 数据不受到表单原来事件的影响,,直接获取绑定的DOM节点数据
<input type="text" id="focousInput"  ref={(event)=>{this.input=event}}  />
     
this.setState({       
    // inputValue:e.target.value  // 原来的事件处理方式
    inputValue:this.input.value   // 采用非受控组件,通过回调函数的Ref实现
})  

(2.常用实例

// 一般用于获取绑定对象的DOM的子类
<ul ref={(event)=>{this.ul=event}}>
    ...
</ul> 
 
通过Ref绑定的标签 this.ul是 对应的标签
console.log(this.ul.querySelectorAll("li"))

(3.使用实例

class Login extends React.Component{ 
    handleSubmit = (event)=>{
        event.preventDefault();            // 阻止默认行为
        let {username,password} = this;
        alert(username.value+password.value);
    }; 
    render(){
        return ( 
            <form onSubmit={this.handleSubmit}>
                用户名:<input ref={c =>this.username = c} type="text" /><br />
                密码:<input ref={c =>this.password = c} type="password" /><br />
                <button>登录</ button>
            </form>

        )
    }
}
(3.高阶函数和函数柯里化

(1.高阶函数

如果一个函数满足下面两个条件之一,就是高阶函数

  1. 返回值是一个函数

  2. 接收的参数是一个函数

  3. 常见的高阶函数:Promise setTimeout arr.map()


// 主动触发函数,以获取一个回调函数
saveFormData = (dataType) =>{
    return (event) =>{
        this.setState({[dataType]:event.target.value})
    }    // 通过【】让 dataType变成key
};

render(){
    return ( 
    <form onSubmit={this.handleSubmit}>
        用户名:<input onChange={this.saveFormData("username")}  type="text" /><br />
        密码:<input  onChange={this.saveFormData("password")}  type="password" /><br />
        <button>登录</ button>
    </form> 
    )
}

(2.函数的柯里化

  1. 通过函数调用持续返回函数的方式,实现多次接收参数,最后统一处理的函数编码方式
// 执行多次返回的回调函数
function sum(a){
    return (b) =>{
        return (c) =>{
            return a+" "+ b + " " +c;
        }
    }
}
console.log(sum(1)(2)(3))

(3.不使用函数柯里化(受控组件)

  saveFormData = (dataType,value) =>{
  	this.setState({[dataType]: value})     // 通过【】让 dataType变成key
  };

 render(){
    return ( 
        <form onSubmit={this.handleSubmit}>
            用户名:<input onChange={ (event) => this.saveFormData("username",event.target.value)}  type="text" /><br />
            密码:<input  onChange={ (event) => this.saveFormData("password",event.target.value)}  type="password" /><br />
            <button>登录</ button>
        </form> 
    )
}

9.component生命周期

(1.生命周期

什么是组件的生命周期?

  1. 组件从创建到死亡它会经历一些特定的阶段。

  2. React组件中包含一系列勾子函数(生命周期回调函数),会在特定的时刻调用。

    componentWillMount 组件将要完成

    componentDidMount (完成前都有render) 组件构建完成

    componentWillReceiveProps(子组件) 组件将接收父组件的信息

    shouldComponentUpdate 确认组件是否应该更新

    componentWillUpdate 组件将要更新

    componentDidUpdate 组件更新完成

    componentWillUmmount 组件将要卸载

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4bfAFV17-1661413920649)(C:\Users\Icy-yun\AppData\Roaming\Typora\typora-user-images\image-20211214154916127.png)]

class Life extends React.Component {
    // 构造器
    constructor(props) {
        console.log('count---constructor');
        super(props);
        this.state = {count:0};
    }


    add = ()=>{
        // 获取原来的状态
        let {count} = this.state;
        this.setState({count:count+1})
    };



   // ------------------  第一-----------------
    // 1.组件将要挂载的勾子
    componentWillMount(){
        console.log('count---componentWillMount');
    }
    // 2.组件已经挂载的勾子
    componentDidMount(){
        console.log('count---componentDidMount');
    }


    // --------------------第二 -------------------
    //  3.组件将会接收props的属性,
    // 只有在组件已经存在dom中,才会执行,第一次加载组件的时候不会执行。
    // componentWillReciveProps(){
        console.log("child----componentWillReciveProps")
    }


    // 4.应该组件更新吗?
    // 组件控制组件更新的”开关“ , 需要有一个返回值,确认是否更新
    shouldComponentUpdate(){
        console.log("Console. ---- showComponentUpdate");
        return true;       // 是否允许更新
    }

    // 5.组件将要更新
    componentWillUpdate(){
        console.log("Console. ---- componentWillUpdate");
    }
    
    // 6.组件更新完成的勾子
    componentDidUpdate(){
        console.log("Console. ---- componentDidUpdate");
    }
    
    
    -----强制更新------
    // 强制更新按钮的回调
    force = ()=>{
           this.forceUpdate();
     };
    
    
    // -------------------第三 -----------------------
   // 卸载组件按钮的回调
    death = ()=>{
         // 卸载组件所在的节点。
        ReactDOM.unmountComponentAtNode(document.getElementById("test"))
    };
    
    // 7.组件将要制卸载的勾子
    componentWillUnmount(){
        console.log('count---componentWillUnmount');
    }


    render(){
        console.log('count---render');
        return (
            <div>
                <h3 >当前求和为:{this.state.count}</h3>
                <button onClick = {this.add}>点我+1</button>
                <button onClick = {this.death}>点击卸载</button>
            </div>
        )
    }
}

子组件优化:在父子之前传递数据,不允许同时进行页面的刷新, 这会导致性能的卡慢。

只有在props数据改变时需要的时候才更新组件

// 子组件
shouldComponentUpdate( nextProps,nextState){
    if(nextProps.content !==this.props.content){
         return·true;         // 只有在下一次传递的值不是当前的props的值, 就允许更新
    }else{
       return false;
    }
}

(2.新生命周期

原生命周期:

  componentWillMount  已经被修改 (需要添加UNSAFE_)

  componentWillUpdate   已经被修改 (需要添加UNSAFE_)

  componentWillReceiveProps  已经被修改 (需要添加UNSAFE_)

未来版本也可以被删除, render之前的 Will 将会被移除



getDerivedStateFromProps                  // 从props中获取派生的state状态  (新)

componentDidMount  (完成前都有render)       组件构建完成


getSnapshotBeforeUpdate(preProps,preState)  // 在更新之前获取一个快照, 返回的值可以被componentDidUpdate利用 (新)

shouldComponentUpdate                     确认组件是否应该更新

getSnapshotBeforeUpdate()                  // 在更新之前获取新数据的高度
  
componentDidUpdate(prevProps, prevState, oldHeight) // 组件完成更新


componentWillUmmount                      组件将要卸载

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iinWulEN-1661413920650)(C:\Users\Icy-yun\AppData\Roaming\Typora\typora-user-images\image-20211214160811156.png)]

// ------------------ 第一 -------------


// 从props中获取派生的state状态  (新)
static getDerivedStateFromProps(props,state){
    console.log("count-------getDerivedStateFromProps",props,state);
    // state.object
    // null
    return null;  // 值会继续更新,
    // return props  // 值将不变
}
 
// 组件将要挂载完毕的勾子 
componentDidMount(){
    setInterval(()=>{
        let {newsList} = this.state;
        let news = '新闻' + (newsList.length + 1);
        // 更新状态
        this.setState({newsList:[news,...newsList]})
    },1000)
}

// ------------------ 第二 -------------

// 在更新之前获取一个快照, 返回的值可以被componentDidUpdate利用 (新)
// 常用于滚动计算。
getSnapshotBeforeUpdate(preProps,preState){
    console.log("count------getSnapshotBeforeUpdate",preProps,preState);
    return 'hello';
}


// 控制组件更新的”开关“ , 需要有一个返回值,确认是否更新
shouldComponentUpdate(){
    console.log("Console. ---- showComponentUpdate");
    return true;
}

   
// 在更新之前获取数据的高度
getSnapshotBeforeUpdate(){
    // 组件更新之前的高度
    let oldHeight = this.refs.list.scrollHeight;
    return oldHeight;
}
// 组件完成更新
componentDidUpdate(prevProps, prevState, oldHeight) {
    // 组件更新之后的高度
    let newHeight = this.refs.list.scrollHeight;
    this.refs.list.scrollTop += newHeight - oldHeight;
}
 
// 组件将要制卸载的勾子
componentWillUnmount(){
 	console.log('count---componentWillUnmount');
}
 
(3.父组件渲染流程

一、初始化阶段:由ReactDOM.render()触发—初次渲染
1. constructor()
2. componentwillMount()
3. render()

  1. componentDidMount()
    (一般在这个钩子中做一些初始化的事,例如:开启定时器,发送网络数据,订阅消息)

二、更新阶段:由组件内部this.setSate()或父组件render触发

  1. shouldComponentUpdate()

  2. componentwillupdate()

  3. render()

  4. componentDidUpdate() // 一般完成前的操作都是render渲染操作。

三、卸载组件:由ReactDOM.unmountComponentAtNode()触发

  1. componentwillUnmount()
    (一般在这个钩子中做一些收尾的事,例如:关闭定时器,取消订阅消息)


class A extends React.Component {
    // 初始化状态
    state = {carName:'奥迪'};

    //换车的回调
    changeCar = ()=>{
        this.setState({carName:'宝马'})
    }

    render(){
        console.log('count---render');
        return (
            <div>
                <h3 >我是A组件</h3>
                <button onClick={this.changeCar}>换车</button>
                <B carName ={this.state.carName} />
            </div>
        )
    }
}
class B extends React.Component {
    // 组件将要接收新的props的钩子---第一次接收props的时候不会执行,只有后来props更新了,才会触发

    componentWillReceiveProps(props){
        console.log('B----componentWillReceiveProps',props)
    }

    // 控制组件更新的”开关“ , 需要有一个返回值,确认是否更新
    shouldComponentUpdate(){
        console.log("B---- showComponentUpdate");
        return true;
    }

    // 控制组件更新的”开关“
    componentWillUpdate(){
        console.log("B ---- componentWillUpdate");
    }

    // 组件更新完成的勾子
    componentDidUpdate(){
        console.log("B ---- componentDidUpdate");
    }

    render(){
        console.log('B---render');
        return (
            <div>
                <h3 >我是B组件</h3>
                <p>我的车是:{this.props.carName}</p>
            </div>
        )
    }
}


// 2.渲染组件到页面
ReactDOM.render(<A  />,document.getElementById("test"))

10.PropTypes的数据校验

在prop-types中有PropTypes模块

import PropTypes from 'prop-types';

XiaojiejieItem.propTypes = {
    avaname:PropTypes.string.isRequired,         //必需传参
    content:PropTypes.string,        // 期望传参
    index:PropTypes.number,
    deleteItem:PropTypes.func
}

XiaojiejieItem.defaultProps = {
     avaname:"阿美服务"          // 默认值
}


// 这是对父类传递给子类的数据校验

(设置在子组件的内部。相当于静态变量)
类内是static ,而类外是 类名 + .    

11.setState的使用

1.setState的基本语法
setState的两种写法对象式的setState:

  1. setState(statechange,[callback]) 对象式
    1. stateChange为状态改变对象(该对象可以体现出状态的更改)
    2. callback是可选的回调函数,它在状态更新完毕、界面也更新后(render调用后)才被调用函数式的setState:
  2. setState(updater,[callback]) 函数式
  3. updater为返回stateChange对象的函数。
  4. updater可以接收到state和props.
  5. callback是可选的回调函数,它在状态更新、界面也更新后(render调用后)才被调用。
  6. 总结:
  7. 对象式的setState是函数式的setState的简写方式(语法糖)
  8. 使用原则:
- 如果新状态不依赖于原状态===>使用对象方式
- 如果新状态依赖于原状态===>使用函数方式
- 如果需要在setState()执行后获取最新的状态数据,要在第二个callback函数中读取

注意:setState是异步的,所以需要 在回调结束后再打印结果

addList(){
    this.setState({
        list:[...this.state.list,this.state.inputValue] ,
        inputValue:""
    },()=>{
        // 添加一个回调函数
        console.log(this.ul.querySelectorAll("li").length)
    })
}
// 获取原来的状态
// let {count} = this.state;
// let count = this.state.count;
// 更改状态
// 方式一: 对象形式
// this.setState({count:count+1},()=>{
//     console.log(this.state.count)
// })

// 方式二: 函数方式
//this.setState(state=>({count:state.count+1}))
this.setState((state,props)=>{
    console.log(state,props)               // 可以接收到state和props
    return {count:state.count+1}
})

2.state的基本概念

  1. 状态的处理
    • 获取 isHost的值
      let isHot = this.state.isHot;
    • 状态要通过setState更新,这是一种合并,而不是替换

​ this.setState({isHot:!isHot})

  1. 总结state

    1. state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
    2. 组件被称为"状态机"通过更新组件的state来更新对应的页面显示数据(重新渲染组件),组件里面维护着状态,状态里面存着数据,在更新状态里面的数据时,组件就能重新渲染
  2. 注意点

  3. 组件中render方法中的this为组件实例对象

  4. 组件自定义的方法中this为undefined ,如何解决?
    a.强制绑定this:通过函数对象的bind
    b.赋值语句+箭头函数

  5. 状态数据,不能直接修改或更新


<script type="text/babel">
// 1。创建虚拟DOM
const my = "title";
class Weather extends React.Component{
    constructor(props){
        super(props);
        this.state = {isHot:true,wind:'大风'};
        this.changeWeather = this.changeWeather.bind(this);
        // 将当前原型对象中的changeWeatherthis改变为实例的this对象。
        // 前面的changeWeather可以改变,就是一个变量而已
    }
    // 页面的渲染,执行了1+n次
    render(){
        console.log(this);
        let {isHot,wind} = this.state;          // 将this.state中的数据解构出来
        return <h1 id={my} onClick={this.changeWeather}>今天天气很{this.state.isHot ? "炎热" : "凉爽"  }</h1>
    }
    // 绑定事件
    changeWeather(){
        //changeweather 方法在哪?changeweather方法在类的原型对象上,供Weather实例调用
        // 通过wWeather实例调用changeweather时,changeweather中的this就是Weather实例
        //类中的方法默认开始局部的严格模式,所以当类内部调用changeWeather方法的时候,this就是undefined

        //console.log(this.state.isHot)
        // 获取 isHost的值
        let isHot = this.state.isHot;
        // 状态要通过setState更新,这是一种合并,而不是替换
        this.setState({isHot:!isHot})
        // 注意:状态state不能直接修改,this.state.isHot = !isHot无效

    }
    //使用赋值语句,可以将方法添加到实例本身,而不是类的原型上面
    //自定义的方法--要用赋值语句+箭头函数,箭头函数没有this,会向上去同级寻找this
    changeWeather = ()=> {
         let isHot = this.state.isHot;
         this.setState({isHot:!isHot})
     }
}
// 2.渲染虚拟DOM到页面
ReactDOM.render( <Weather />,document.getElementById( 'test'))

</script>     

12.html标签解析

1.dangerouslySetInnerHTML解析

注意:为什么会是dangerously解析html标签呢?那是因为渲染html标签容易受到xss攻击,所以一般都不建议大量解析html字符串。

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

2.其它知识

1.label友好型标签,for里面填写input的id值,点击label的时候,就会解锁focus与datalist很类似
    <label for="focousInput">增加服务</label>
    <input type="text" id="focousInput" className="input"  />
    
2.input标签中的checked 如果需要设置变量,则需要把checked改成 defaultChecked 
                   

3.vscode快捷键

SnippetRenders
imrImport React
imrcImport React / Component
imrdImport ReactDOM
imrsImport React / useState
imrseImport React / useState useEffect
imptImport PropTypes
impcImport React / PureComponent
ccClass Component
cccClass Component With Constructor
cpcClass Pure Component
fcFunction Component
cdmcomponentDidMount
uefuseEffect Hook
cwmcomponentWillMount
cwrpcomponentWillReceiveProps
gdsgetDerivedStateFromProps
scushouldComponentUpdate
cwucomponentWillUpdate
cducomponentDidUpdate
cwucomponentWillUpdate
cdccomponentDidCatch
gsbugetSnapshotBeforeUpdate
sssetState
ssfFunctional setState
usfDeclare a new state variable using State Hook
renrender
rpropRender Prop
hocHigher Order Component

13.props的基本使用

(1.传递props属性,  标签属性
<li>姓名:{this.props.name}</li>
ReactDOM.render(<Person  name="LILI" sex="女" age="18"/>,document.getElementById("test2"))


​ (2.批量传递…pops属性(展开运算符)
​ let p = {name:‘LILI’, sex:“女”,age:18}
​ // 在babel中,展开运算符展开对象,只能在标签组件通过属性传递props的时候使用
​ ReactDOM.render(<Person {…p}/>,document.getElementById(“test2”))


​ (3.限制props传递的属性, 使用propTypes


注意: props是只读的,不能修改它的内容
1.创建类式组件 2.渲染组件到页面


​ (4.简写形式, 如果需要把内容放在类里, 需要添加静态前缀static


​ (5.构造器中使用props
​ //基本用不到构造器—–了解一下就行
​ constructor( props){
​ //构造器中是否传入props,super中是否传入props,
​ //取决于:是否在构造器中通过this获取props
​ super(props);
​ console.log(this.props)
​ }

二、 React高级学习

1.westorm快捷键

rcc  +  tab 键  用es6模块系统创建一个React组件类

rccp  +  tab键      用es6模块系统创建一个React组件类,带有propTypes

rsf  + tab键      以命名函数的形式,创建一个没有状态的React组件

2.组件化编码的过程

  1. 拆分组件,拆分界面,抽取组件
  2. 实现静态组件,实现静态效果
  3. 实现动态组件
    1. 动态 显示初始化数据
      • 数据类型
      • 数据名称
      • 保存的位置
    2. 交互的事件

1.引入样式与图片

(1.引入css样式

1.普通使用方法

在js文件定义css文件

直接在js文件中
import './login2.css';

使用HashRouter(不推荐)

import {HashRouter} from 'react-router-dom';

2.js引入模块css

css文件名使用xxx.module.css
在js中引入
import styles from './login.module.css';

标签中使用变量
 <button className = {styles.button}>提交</button>

3.引入jsx 样式(不建议)

<style jsx>
{` 
    .HorizontalScrollTrackInner{
        top:14px!important;
        background:red;
        height:8px;
    }
    .HorizontalScrollTrack::after{ 
        height:12px!important;
        background:#b8b8b8!important;
    }
`}
</style>
(2.引入img图片
图片需要单独作为变量引入。
import Logo from '../../assets/images/user.jpg' 

 <img src={Logo} alt="" />  

图片加速网站 : https://images.weserv.nl/?url= 如果有https://,则需要先去除

<img src={"https://images.weserv.nl/?url="+movieObj.img.replace('https://','')} alt={movieObj.title} width="300" height="300"/>

头部检测来源域(也可以实现图片展示)

在index.html头部添加
<!--禁止检查来源域-->
 <meta name="referrer" content="never">

其它加速网站

以下是网络中收集的一些图片镜像缓存服务,持续更新。

1.https://img.noobzone.ru/getimg.php?url=   (失效)
2.https://collect34.longsunhd.com/source/plugin/yzs1013_pldr/getimg.php?url=
3.https://ip.webmasterapi.com/api/imageproxy/ (失效)
4.https://images.weserv.nl/?url=(失效)
5.https://pic1.xuehuaimg.com/proxy/
6.https://search.pstatic.net/common?src= (失效)
(3.引入nanoid随机值模块

nanoid组件

uuid

yarn add nanoid 生成不重复的id

import {nanoid} from 'nanoid';

nanoid()  获取唯一的id值
(4.引入less 预编译模块

此方式很容易解决css样式污染的问题!所以建议使用less,或者 sass开发。

(1.安装模块

yarn add less less-loader@4.x.x

注意:一定要把版本降到5.0.0以下,否则会重复出现以下报错!

.....Less Loader has been initialized using an options object ...

(2.找到webpack.config.js

  1. react脚本架默认css 和 sass , 所以需要修改webpack配置,使其支持less
  2. react-app-rewired 组件是继承了react-scripts模块的内容,所以webpack配置是在react-scripts包中。
react-scripts/config/webpack/webpack.config.js

(3.修改webapck.config.js配置


// 在style files regexes 下方添加匹配规则 , 支持less
const lessRegex = /\.less$/;
const lessModuleRegex = /\.module\.less$/;
 
        
// 向getStyleLoaders函数中注册Less 
const getStyleLoaders = (cssOptions,lessOptions, preProcessor) => {
    
    ....
    {
    	loader: require.resolve('less-loader'),
    	options: lessOptions,
    },
    ....
 }
  
  
// 搜索sassRegex, 仿写sass,在上方添加添加less-loader插件,
{
   test: lessRegex,
   exclude: lessModuleRegex,
   use: getStyleLoaders(
   {
      importLoaders: 2,  // 注意
      sourceMap: isEnvProduction
         ? shouldUseSourceMap
         : isEnvDevelopment,
          modules: {
            mode: 'icss',
          },
       },
       'less-loader'  // 注意
       ), 
      sideEffects: true,
   }, 
   {
     test: lessModuleRegex,
     use: getStyleLoaders(
     {
       importLoaders: 2,  // 注意
        sourceMap: isEnvProduction
         ? shouldUseSourceMap
          : isEnvDevelopment,
           modules: {
            mode: 'local',
              getLocalIdent: getCSSModuleLocalIdent,
        },
       },
       'less-loader' //注意
     ),
  },

(4.然后重启项目。(配置项一定要重启项目)

注意:less文件不能使用 “ // ” 直接注释css行样式

(5.打包后的static

因为打包后的文件里面包含重定向,需要代理访问,所以tomcat, apache,浏览器都不能直接解析,而nginx, iis可以解析。

修改webpack.config.js中的文件

删除static/前缀,  修改{ publicPath: '../../' } 为 { publicPath: '../' }


2.组件之间传值

(1.pops父子传值

1.父传子

父组件将状态信息或者数据标签属性的方式,传递给子组件,子组件通过props接收

  // 父组件
  <List todos={this.state.todos}/>
   
   // 子组件
  const {todos} = this.props;

2.子传父

父组件通过标签属性的方式将函数传递给子组件,而子组件调用父组件传递的函数进行数据的添加。

 // 父组件
 <Header addTodo = {this.addTodo}/>
 
 // 子组件
 // 调用父组件传递的函数,将输入的数据通过函数参数传递给父组件
  this.props.addTodo({id:nanoid(),title:target.value,done:false})

3.单向数据流

父组件 的数据传递到子组件,反过来则不可以。子组件不能直接改变父组件的值,即使改变,浏览器也会发出警告。

会导致数据流向无法确认, 比如在子组件内 this.props.list = [] , 将出现异常

state状态一般用于当前组件数据,而props一般用于子组件数据,并且 一般使用map 修改数组数据, 使用filter删除数据

1.// 过滤map
     changeTodo = (id,done)=>{
         // 获取原始数据
         let {todos} =this.state;
         // 处理加工数据,       更改数据, 每次遍历会返回所有的值
         let newTodos = todos.map((todo)=>{
             if(todo.id === id){
                 return {...todo,done:done};
             }
             return todo;
         })
         this.setState({todos:newTodos})
     };

2. // 删除 filter
    deleteTodo = (id)=>{
        // 获取原始数据
        let {todos} = this.state;
        // 筛选数据               每次只返回结果为true时的数据
        let newTodos = todos.filter((todo)=>{
            return todo.id !== id
        })
        // 更新数据
        this.setState({todos:newTodos})

    }
 3. // 求和 reduce
        let doneCount = todos.reduce((pre,todo)=>{
            return pre+(todo.done ? 1: 0)
        },0);
(2.pubsub-js兄弟传值
  1. 适用于任何组件之间的通信
  2. 需要在componentWillUnmount取消订阅。
安装模块  npm install pubsub-js

引入模块  import PubSub from 'pubsub-js'

发布信息
      PubSub.publish('data',{isFirst:false,isLoading:true})

订阅信息  
      componentDidMount(){
          // 订阅消息
          // 可以先取消上一次的订阅
          PubSub.unsubscribe("data") 
          this.token = PubSub.subscribe("data",(msg,stateObj)=>{
          this.setState(stateObj);
          	console.log('list也收到订阅信息了',msg)
          })
      }
取消订阅
       componentWillUnmount(){
           // 取消消息订阅
           PubSub.unsubscribe(this.token); // 从this中获取token数据
       }

嵌套越多,会产生回调地狱。

3. 异步请求数据

(1.axios的使用

1.基本概念

  1. axios 轻量级,是封装xmlHttpRequest对象的ajax
  2. 是promise风格,返回的是一个promise对象
  3. 可以用在浏览器端和node服务器端
yarn add axios 

 // axios.get('http://localhost:3000/search/movies?q='+keyword).then(res=>{
        //     console.log(res.data)
        //     PubSub.publish('data',{movies:res.data,isLoading:false});  // 修改单独的数据
        // },err=>{
        //     PubSub.publish('data',{err:err.message,isLoading:false});  // 修改单独的数据
        // })

跨域是客户端收到前,被浏览器拦截,受到同源限制。

使用xml 的ajax引擎, 产生了同源策略。而使用同端口的代理服务器,就可以跨域访问。

2.变形axios

最重要的是在内部函数中 添加 **async ** 进行异步等待。

 // axios 返回一个Promise对象, await可以将promise对象解析出来
   useEffect(()=>{ 
        const fetchData = async () => {
            const result = await axios(
                'https://hn.algolia.com/api/v1/search?query=redux',
            );
            setData(result.data);  
        };
      fetchData(); 
   },[])
(2.Fatch 的使用

传统 Ajax 已死,Fetch 永生 - SegmentFault 思否

XMLHttpReqquest

fatch是先与服务器获得连接,然后再获取数据。

  // 使用fetch获取数据
        // fetch('http://localhost:3000/search/movies2?q='+keyword).then(res=>{
        //     console.log("数据成功")
        //     return res.json();
        // }).then(res=>{
        //     console.log("请求成功",res)
        // }).catch(e=>{
        //     console.log("请求失败",e)
        // })
        // 先与服务器连接,再获取数据
        try{
            let response = await fetch('http://localhost:3000/search/movies2?q='+keyword)
            let data  = await response.json();
            this.setState({isLoading:false}); 
        }catch(err){
            console.log("请求失败",err)
            this.setState({isLoading:false}) 
        }    
(3.代理跨域访问(?)
在src文件下新建 setupProxy.js文件,复制以下代码搞定(这个文件会被自动识别,不用去导入)

const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
    app.use(
        '/api',
        createProxyMiddleware({
            target: 'http://localhost:8888/',
            changeOrigin: true,
            pathRewrite: {
                '^/api': '',
            }
        })
    );
    app.use(
        '/apc',
        createProxyMiddleware({
            target: 'http://localhost:7777/',
            changeOrigin: true,
            pathRewrite: {
                '^/apc': '',
            }
        })
    );
};  

4.router路由的使用

1.SPA单页应用

单页应用只有一个页面,当页面资源加载完毕,页面将不会因为用户的点击而进行跳转。取而代之的是路由机制,实现网页内容的替换。 数据都要通过ajax异步获取。

2.router的使用

router是一个映射关系,可以根据 路由规则找到不同的函数或组件。

react-router是一个插件库,专门用户实现SPA应用

工作过程:当浏览器的path变为/test时,当前路由组件就会变为Test组件

3.路由原理

1. history模式, 直接使用h5推出history的api      History.createBrowserHistory()
2. hash模式, (锚点)                          History.createHashHistory

4.安装模块

yarn add react-router-dom@5.1.0

最新版本有问题,所以确定一下路由版本

5.注意事项

  1. Route和 Link标签都需要被BrowserRouter 包裹, 如果 路由 不在相同的 BrowserRouter, 则无法自由切换组件。不同的路由器是属于不同的模块.

  2. 建议在index.js中使用同一个BrowserRouter包裹, 因而下面的所有组件就会在同一个路由器中,不仅保证了组件的动态切换特性,也减少了代码的冗余性

基本使用

(1.Link路由导航
  1. 在多页应用中,使用a标签 节切换不同的页面

  2. 在单页应用中,就不允许使用a标签进行跳转。 在react使用 "Link’ 标签编写路由链接, 因为它最后会转化为a标签的形式。

import {Link} from 'react-router-dom';

// 路由链接
<Link to="/home" >首页</Link>
(2.Route路由规则

“Route” 标签 用于展示导航指向的组件内容

import {Route} from 'react-router-dom';
//绑定路由或者注册路由

<Route path="/home" components={Home}></Route>
(3.BrowserRouter注册路由

区别:BrowserRouter和HashRouter模式

  1. 底层原理不一样:

    • BrowserRouter 使用H5的history APi,不兼容IE9 及以下的版本

    • hashRouter使用的是URL

  2. path表现形式不一样

    • HashRouter的路径包含#
  3. 刷新 后对路由state参数的影响

    • BrowserRouter没有任何影响 ,因为state保存在history对象中

    • HashRouter刷新后会导致路由State参数的丢失!

(4.NavLink 高亮路由链接*

活动的链接都会添加默认的active类名,可以通过 activeClassName指定活动时 显示的类名

注意: NavLink只会监听路径的变化 ,params传参可以启动。 但search传参,则认为是相同的路由。

import {NavLink} from 'react-router-dom';


{/*默认会给激活的路由链接添加active 类名*/}
<NavLink className="active" to="/home">首页</NavLink> |

{/*也可以自定义链接, 使用activeClassName来显示活动时的类名*/}
<NavLink activeClassName="my-active" to="/home">首页</NavLink> | 
<NavLink activeClassName="my-active" to="/about">首页</NavLink> |

4.封装NavLink组件

  • 标签体的内容可以作为 一个特殊的props传递给组件,key是children, value是标签体的内容

  • 组件内可以通过this.props.children获取标签体的内容

---- app.js组件

    {/*标签体的内容是一个特殊的属性children*/}
    <MyNavLink to="/home" a={1} b={2}>Home</MyNavLink>
    <MyNavLink to="/about"  >about</MyNavLink>



----自定义MyNavLink组件 
    // 可以动态向子组件传入属性属性
    // NavLink 会被转化成超链接a标签,  children属性值会自动填充到NavLink标签体中
    <NavLink activeClassName="my-active"  {...this.props}/>


(5.Switch单路由匹配机制
  • 一个路由规则,对应多个组件。 这样会导致严重的效率问题

  • 使用Switch标签包裹所有的路由规则,只会匹配首个单独的组件。

  • Switch有效地提高了react运行的效率。

import {Switch} from 'react-router-dom';

<Switch> 
       <Route path='/home' component={Home}></Route>
       <Route path='/about' component={About}></Route>
       <Route path='/about' component={Test}></Route>
       {/*编写路由规则 或者 注册路由*/}
</Switch>

(6.DocumentTitle标题模块

安装模块

yarn add react-document-title

使用模块


import  DocumentTitle from 'react-document-title' 


<Suspense fallback={<h1>Loading</h1>}>
     {
     	routerMap.map((item,index)=>{
            return ( 
             <Route path={item.path} component={item.component} exact={item.exact} key={index}> </Route>      			 )
         })
       }
</Suspense>


// 对当前的总页面进行设置
function Home(){ 
    return (
        <DocumentTitle  title={activeTitle ? activeTitle : defaultTitle}>
             ....
        </DocumentTitle>
    )
} 
export default Home;


// 监听当前的路由变化(改变标题)
  const [activeTitle,setActiveTitle] = useState(null) 
  const defaultTitle = '山口岩智能水库云平台'
  useEffect(()=>{ 
        let route = window.location.href;
        subRouterMap.some((item,index)=>{
            if(route.endsWith(item.path)){  
                setActiveTitle(item.title)
            }
        }) 
  },[location])  

(7.withRouter路由转化组件
  • 一般组件 没有 this.props.history相关的api, 它没有经过 的 使用。
  • 路由组件 才具有 histroy, location, match 这三个参数
  • withRouter可以加工 一般组件,让一般组件 具备路由组件所特有的api
  • withRouter是一个函数, 接收一个一般组件 ,返回一个加工后的新组件,这个 组件具备路由组件的一些特性

引入withRouter

import {withRouter} from 'react-router-dom';

使用withRouter

export default withRouter(Header);
(8.lazy+Suspense懒加载

lazyload懒加载,是对路由引入模块的时机进行控制。

  • 需要从react 包中引入lazy 和 Suspense 方法

  • laay动态引入, **Suspendse**包裹路由规则,定义响应期间的回调。

  • import React,{lazy,Suspense} from 'react';
    
    
    const Home = lazy(()=>import('./Home'))
    const About = lazy(()=>import('./About'))     // 懒加载
    
    <Suspense fallback={<h1>Loading</h1>}>
            <Route path='/home' component={Home}></Route>
            <Route path='/about' component={About}></Route>
    </Suspense>
    
    
(9.Redirect路由重定向
  • 当所有内部都没有匹配到结果的时候,就会重定向目标路由。
  • 一般放在路由规则的底部
import {Redirect, Link} from 'react-router-dom'

1.标签式重定向
<Redirect to="home"/>
   
   
2.history编程式重定向
this.props.history.push('/home/')


vue中使用的是

router.push({})  或者  $router.push({  })

path:"/:pathMatch(.*)"
component:ErrorPage      全局的重定向。
(10.push编程式路由(props)
  • 在js中进行路由的跳转

  • 绑定的事件向函数传入参数,如果需要传入参数,则需要将回调函数传入参数中,否则 会自动执行;

  • 如果不需要传入参数,可以直接填写函数名称

跳转函数

    pushShow = (id,title)=>{
        // 进行push跳转
        // params传参
        // this.props.history.push(`/about/message/detail/${id}/${title}`)

        // search传参
        this.props.history.push(`/about/message/detail?id=${id}&title=${title}`)

        // state传参
        // this.props.history.push(`/about/message/detail`,{id,title})

    }
   replaceShow = (id,title)=>{
        // 进行push跳转
        this.props.history.replace(`/about/message/detail/${id}/${title}`)
    }

    // 后退
    goBack = ()=>{
        this.props.history.goBack();
    }
    // 前进一步
    goForward = ()=>{
        this.props.history.goForward(-2);
    }
    // 前进( n  / -n)  
    go = ()=>{
        // this.props.history.go(2);
        this.props.history.go(-2);
    }

事件触发

                                    <button style={{'color':'red'}} onClick={()=>{this.pushShow(item.id, item.title)}}>push跳转</button>
                                    <button style={{'color':'red'}} onClick={()=>{this.replaceShow(item.id, item.title)}}>replace跳转</button>
                                    
<button onClick={this.goBack}>后退</button>            // 不传入参数,可以不使用回调函数
<button onClick={this.goForward}>前进</button>
<button onClick={this.go}>Go</button>                          

区别:push与replace模式

  • 堆栈的思想, 默认push方式
  • 新的链接会被压入栈中,成为栈顶元素
  • replace是替换当前在缓存中保存的路由路径。
  • 在路由链接中使用 replace={true} 或者 replace 转换为replace模式
(12.useLocation 路由监听

前提: 安装react-router-dom@ 5.1.0 版本,这是新特性

import { useLocation } from 'react-router-dom'; 

 const location = useLocation(); 
 
 
 // 监听路由的变化 。  
 useEffect(()=>{  
    // 从对象字符串中转换成对象
    let search = qs.parse(props.location.search.slice(1))
    let {title} = search;
    setTitle(title)
 
    console.log("SelfList路由数据",props,title)
    console.log("pathname is",obj)
    if(title=='默认收藏' || title == ''){ 
        setMusicList(getMusicList) 
    }else{
        setMusicList(getMusicList2) 
    }
 },[location.search])
(13.Route的exact属性
(1.exact精确匹配

模糊匹配:

  • 路由链接尾部可以添加更长的路径
  • 路由规则是只有头部相同,但没有对应的精确路径。

默认模糊匹配


   <MyNavLink to="/about/abc"  >about</MyNavLink>
   <Route path='/about' component={About}></Route>

精确匹配:

  • 使用exact开启精确匹配 exact={true} 或者 exact。

  • 一般只有首页才会使用精确匹配“/”

  • 只有路由链接和路由规则相匹配的结果才能正常返回结果

  • 建议不使用开启严格匹配,有时候开启会导致无法匹配二级路由

<Route exact={true} path='/home' component={Home}></Route>   
(2.嵌套路由(二级)
  • 二级路由必须先匹配到上一级路由。
  • 二级路由是层层嵌套,上一级使用精确匹配会导致无法匹配二级路由。
  <div className="nav">
      <MyNavLink to="/about/new">news</MyNavLink>
      <MyNavLink to="/about/message">Message</MyNavLink>
      </div>
      {/*注册路由*/}
      <Switch>
          <Route path="/about/new" component={News} />
          <Route path="/about/message" component={Message} />
          <Redirect to="/about/new"/>
      </Switch>
(3.map后台动态路由
  1. { } 说明里面是一个Js语法 (包含变量) ,key = {index}

  2. 而vue中 v-bind也说明字符串代表的是一个变量 v-bind:key = “index”

let routeConfig =[
    {path: '/' ,title: 博客首页' ,exact:true, component:Index},
    {path : '/video',title: '视频教程' , exact:false, component:Video},
    {path : '/workplace',title:'职场技能' ,exact:false, component:Workplace},
]


<ul>        
    {
      routeConfig.map((item,index)=>{
         return (<li key={index}> <Link to= {item.path} >{item.title} >)
      }) 
    }
</ul>
<div className = "rightMain">
  {
     {/*router-view*/}
      routeConfig.map((item,index)=>{
           return (<Route key={index}  path={item.path} component={title}>)
      })
  }

(4.Fragment模板标签
  • react包中有**React, React.Component**

    React是声明当前组件是基于React的组件

  • react-dom包有**ReactDom**

    ReactDom是将组件挂载到页面的模块

    一般的组件中,只能返回一个单独的标签,而使用**弹性布局**的时候则会出现多余的标签。

    在react包中有**React.Fragment** , 可以去除最外层标签。

    使用标签代替

    标签,但前者标签不会挂载到DOM上,和vue的template一样的功能

创建组件

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

class App extends Component{        // 继承React.Component组件
  render(){
    return (
      // <div>        {/*只能被 一个单独的标签包裹div */}
      //   <h1>66666</h1>
      //   <p>Hello World</p> 
      // </div>
      <Fragment>        {/*只能被 一个单独的标签包裹div */}
        <h1>66666</h1>
        <p>Hello World</p> 
      </Fragment>
    )
  }
}

export default App;

挂载DOM

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


ReactDOM.render( 
   <App />,
  document.getElementById('root')
);


(14.路由跳转传参
(1.params路径参数
  • 路由规则使用 :id 或者 :xx
  • 路由链接,可以使用“+” 连接路径 ,但需要使用{} 包围。 (一般数字是要{}包围的)
  • 目标路由组件,可以使用this.props.match.params.xxx 获取传入的参数
 {/*路由链接*/}
 <p key={item.id}>
 <Link className="nav-dark" to={`/about/message/detail/${item.id}/${item.title}`}>{item.title}</Link>
 </p>

{/*<p key={item.id}>
<Link className="nav-dark" to={"/about/message/detail/"+item.id+"/"+item.title}>{item.title}</Link>
</p>*/}


{/*注册路由*/}
<Route path="/about/message/detail/:id/:title" component={Detail}/>


{/*目标路由组件*/} 
console.log("数据是",this.props.match.params.id);
console.log("数据是",this.props.match.params.title);

let {id,title} = this.props.match.params
(2.路由间search参数
  • 传入的参数,会在子组件的lacation中出现

  • query参数一般会叫做 search参数, 它只是? 后面的字符串

  • 内置querystring 可以将字符转换成 对象形式。 (并非是JSON.stringify)

  • search传入 的参数

  • qs.parse 将串行化的字符串转换成对象。 (qs.stringify 功能相反)

  • JSON.parse 是将 类原对象字符串转换成对象。

  • 注册路由

    to={`/about/message/detail?id=${item.id}&title=${item.title}`}
    
  • 获取到的search是urlencoded 编码字符串

  1. 引入querystring模块

    yarn add querystring
    
    import qs from 'querystring';
    
  2. 将串行化的字符串转换成对象

    let search = qs.parse(this.props.location.search.slice(1));
    let {id,title} = search;
    
(3.路由间state参数
  • state参数存在于缓存中。

  • 只有在成功访问后,才会保存这个状态在浏览器中。

  • 直接访问是获取不到state参数。

  • 一般用于传递比较 敏感数据

  • 注册路由

    to={{pathname:'/about/message/detail',state:{id:item.id,title:item.title}}}  
    
  • 获取参数

    
    let {id,title} = this.props.location.state;
    
    
(4.react,vue的比较

1.路径传值 ,props获取数据

// 获取传值数据
let tempId = this.props.match.params.id;
<Link to={'/list/'+item.cid}> {item.title}
// 定义路由规则,  主路由需要 <Router>包围
<Route path = '/list/:id'   component={List}></Route>
// 可以在加载模板后获取
   componentDidMount(){
        console.log(this.props)
        let tempId = this.props.match.params.id;
        this.setState({id:tempId})      // 把数据放在state里面
    }
     
<h1>List-page:{this.state.id}</h1>

2.vue中的router-link 与react的Link

  // vue
    <li> <router-link :to="{path:'/about/article',query:{name:'hello',age:100}}">文章二</router-link></li>
    
   // react 
     <Fragment>
                <h1>作者编号{this.state.author_id}</h1>
                <ul>              {    /**这其实就像v-for */}
                    {
                        this.state.list.map((item,index)=>{
                              return (
                                  <li key={index}>      {/*一定要传入key*/}
                                      <Link to={'/list/'+item.cid}> {item.title}</Link>
                                  </li>
                              )
                        })
                    }
                </ul>
            </Fragment>
    

3. vue中的reactive({ }) 和react的 state

//  vue
let goods = reactive({  data:[]   })

// react
   constructor(props) {
        super(props);
        this.state = {     /* 类似 reactive({}) */
            list:[
                {cid:111,title:"你好美,小美"},
                {cid:222,title:"你好胖,小胖"},
                {cid:333,title:"你好懒,小懒"},
                {cid:444,title:"你好聪,小聪"},
            ]
         }
    }

5.redux的使用

 1.redux是一个专门 用于做状态管理的JS库(不是react插件库),它可以用在react, angular,vue等项目中,基本与react配合使用。    一个组件的状态,多个组件需要共享使用的情况。

 2.相当于vuex 的,是集中式管理react应用中多个组件共享的状态 。因为redux是一个重量级的js库,使用原则是: 只要系统不是非常复杂,就尽量不用。
(1.redux的原理
image-20211221093934648

(1.流程: redux是维护状态,用于集中式管理。

React Components 向Action Creators 进行登记,

Action Creators 向Store 进行提交申请。

Store 告诉 Reducers 去完成这个行为。

Store 收到 Reducers 处理的结果,再去告诉React Components。

(2.三要素: action, store, reducer

(1. action : 动作的对象

包含两个属性,type 标识属性和 data数据属性

(2.reducer : 用于初始化数据,和加工状态

加工时,将旧的previousState 转换成新的 state

(3.store: 存储状态

用于将Action Creators ,store, Reducers连接在一起的对象

(3.安装模块

yarn add redux redux-thunk react-redux

注意:安装完模块后,重新启动项目

(4.使用总结:

  • 虽然容器组件是用于中间交互,但处理数据存储操作的就是窗口组件。

  • 异步性:redux中进行加工操作, 在回调函数中提交更新状态。

  • 隔绝性:在redux中的reducer不能去获取其它的状态数据.

  • 可操作性 :根据特定的参数,在UI组件中执行刷新操作

  • 可继承性 :redux只能在特定的hooks中使用,也可以用于传递给子组件,但允许直接显示在UI组件上。

  • 共享性:可以用于处理当前组件状态state更新过慢的问题

(2.store与reducer(原生 )

处理器: count_reducer.js的文件内容,用于提供服务,操作数据

注意:不要把逻辑业务放在reducer中

import React from 'react';

// 初始化值
const initData = 0

//  count组件reducer,只为count提供服务。本质是函数 
// 有点类似于reduce 进行示例运算
function CountReducer(preState=initData,action) {
    // action的依赖于type和data属性
    console.log(preState,action)
    let {type,data} = action
    switch (type) {
        case 'increment':
            return preState + data;
        case 'decrement':
            return preState - data;
        default :
            return preState;
    }
}

export default CountReducer;

**存储仓库:**store.js的文件,用于创建事务,写入记录

在 createStore调用时,要传入一个为其服务的reducer,

import React from 'react'
import {createStore} from 'redux';


// createStore 创建了store
import CountReducer from './count_reducer'

// 引入 reducer , 帮助store处理操作
const store = createStore(CountReducer);

export default store;

**视图组件:**count.js使用redux,组件监听redux

    componentDidMount() {
        // 监听redux,当state改变时,重新更新页面
        store.subscribe(()=>{
            this.setState({})     // 不能使用this.render() 进行重新渲染。
        })
    }

    increment = ()=>{
        // select的值
        let {value} = this.selectElement;

        // 通知redux,执行加法
        store.dispatch({type:'increment',data:value*1})
 
    }

    <h3>计算结果为:<span>{store.getState()}</span></h3>
                     
   <button value="+" onClick={this.increment}>+</button>
                        

store优化:全局监听redux


// 监听redux,当state改变时,重新渲染组件。
store.subscribe(()=>{
    ReactDOM.render(
        <App />,
        document.getElementById('root')
    );
})

使用流程

  • components监听redux, 创建ActionCreators对象。

  • components通知ActiontCreators, 并向store提交action

  • store将action传递给reducer。 store.dispatch({type:'decrement',data:value*1})

  • reducer进行数据的初始化和加工。

  • store获取返回的状态,并将结果返回给组件,重新渲染页面。 store.getState()

reducer处理

  • reducer的本质是一个纯函数,用于接收两个参数PreState,action, 然后返回加工后的状态

  • reducer 有两个作用: 一是初始化状态,二是加工状态

  • reducer被第一次调用时,是sotre自动触发的, 传递的preState是Undefined, 传递的action是 {type:'@@REDUX/INIT_c.x.1.4} redux的版本信息

  • reducer为什么会被调用再次,因为创建一个活动, 向store分发一个活动的时候,store会将action提交给reducer, 这个时候就会被初始化,然后数据处理,又再次调用了一次。(??)

Action优化:将活动分离出组件

/*
* 为Count组件生成action对象
* */

import {INCREMENT,DECREMENT} from './constant';
// 加法
export const createIncrementAction = data=>({type:INCREMENT,data:data});
// 减法
export const createDecrementAction = data=>({type:DECREMENT,data:data});
/*
*   用于存放action对象中使用的type的值,
* */

export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';
(3.redux-thunk异步(原生)

把异步操作放在创建action的文件里。

store只接收一般对象,如果需要使用异步,则需要安装中间件 redux-thunk

yarn add redux-thunk

异步:store使用thunk中间件,以执行异步

// 使用redux提供的createStore函数来创建store
import {createStore,applyMiddleware} from 'redux';

// 引入reducer,用于store处理操作
import CountReducer from './count_reducer'

// 引入thunk中间件,用于让store支持异步action
import thunk from 'redux-thunk';

// 引入 reducer , 帮助store处理操作
const store = createStore(CountReducer,applyMiddleware(thunk));

export default store;

原count_action.js ,添加以下内容

import store from './store'

// 用于action的异步
export const createIncrementAsyncAction = (data,time)=>{
    return ()=>{
        setTimeout(()=>{
            store.dispatch(createIncrementAction(data))
        },time)
    }
}
(4.combineReducers(原生)
  • 不同组件组成的容器,可以通过redux共享数据
  • Person的reducer和Counter的reducer需要经过 combineReducers 进行合并,合并产生后的总状态是一个对象
  • 传递给store的是总的reducer,在容器组件中获取状态的时候, 也是需要指定key获取.
// 使用redux提供的createStore函数来创建store
import {createStore,applyMiddleware,combineReducers} from 'redux';

// 引入reducer,用于store处理操作
import CountReducer from './reducers/count'
import PersonReducer from './reducers/person'

// 引入thunk中间件,用于让store支持异步action
import thunk from 'redux-thunk';


// 汇总所有的reducer
const allReducers = combineReducers({
    he:CountReducer,
    rens:PersonReducer
})


// 引入 reducer , 帮助store处理操作
export default  createStore(allReducers,applyMiddleware(thunk));

(5.react-redux的原理(新)*
image-20211230174814746

**使用原理 **

首先在容器中注册UI组件,然后可以通过容器组件间接访问UI组件。

  1. UI组件连接容器组件, 容器组件与redux交互,

  2. 容器组件向action Creator 创建提交创建申请,

  3. action被提交到store中,进行集中管理状态,然后通知reducer去处理action,

  4. reducer对数据进行初始化或者加工后,返回一个新的状态。

  5. store将新的状态进行存储,而容器组件可以获取新的状态。

  6. UI组件再向容器组件获取状态。

安装模块

yarn add react-redux

向UI组件中传入store对象

<Count store={store}/>

容器组件: 定义一个Containers容器,Count.jsx

容器用于与redux交互,提交操作方法,返回保存的状态, 简化了redux的一系列操作

// 引入connect ,用于连接UI和redux
import {connect} from 'react-redux';


// 引入Count UI组件
import CountUI from '../Components/Count';


// 接收redux中的状态参数, 传递redux的状态给UI组件
// 映射状态到props
function mapSateToProps(state){
     return {count:state}
}


// 函数返回的对象作为 操作状态的方法传递给UI组件
// 映射dispatch到props
function mapDispatchToProps(dispatch){
    return {
        jia:(data)=>{
            // 修改redux中的状态
            dispatch({type:'increment',data})
        },
        
    }
}

// 使用connect 连接Count UI组件, 并把创建的容器组件暴露出去
export default connect(mapSateToProps,mapDispatchToProps)(CountUI)

**UI组件:**直接使用容器通过 Props传递的方法Component, Count.jsx

 // 调用父组件传递过来的方法
  this.props.jia(value*1);
  
  // 调用父组件传递过来的状态
  this.props.count

容器组件优化: Container容器的优化

容器组件会自动监听redux的变化 ,所以不需要在全局监听redux

// 引入connect ,用于连接UI和redux
import {connect} from 'react-redux';

// 引入Count UI组件
import CountUI from '../Components/Count';


// 引入action
import {createIncrementAction,createDecrementAction,createIncrementAsyncAction} from '../redux2/count_action'

 

export default connect(
    //接收一个参数,这个 参数就是redux中的状态, 传递redux的状态给UI组件
    state => ({count:state}),

    // 返回的对象作为操作状态的方法传递给UI组件
    // 第1种方式: 传函数 (默认方式)
    dispatch=>({
        jia:data => dispatch(createIncrementAction(data))
    }),

    // 第2种方式,传对象(建议)
    {
        jia:data=>createIncrementAction(data),
        
        // 这里虽然直接写action,但react-redux会自动调用 dispatch 派发action
        jia:createIncrementAction,
    }

)(CountUI)

store优化: 全局的Provider

react-redux中还提供了Provider。只要容器中需要使用store,被Provider包裹的组件,就可以通过Provider组件提供store。


import {Provider} from 'react-redux';
import store from './redux2/store'

<Provider store={store}> 
	<App />
</Provider>,
(5.reduce纯函数的规则*

纯函数的规则:

  1. 即始终返回相同的值,无论什么时候调用,比如Math.cos(0)就是纯函数,Math.random()就不是纯函数

  2. 返回结果只依赖于它的参数,不改变参数的值,不使用外部变量

  3. 执行过程里面没有副作用,比如修改外部变量,请求,DOM APl, console.log都不行

为什么要使用纯函数?

  1. 靠谱,不会产生不可预料的行为,也不会对外部产生影响,更容易调试,易于组合,易于并行开发
  2. Redunx中的Reducer必须是一个纯函数
  3. 参数一定时,始终返回相同的值。
  4. 不改变参数的值,不使用外部的变量
  5. 无副作用。不改变外部的变量,也不请求数据和输出。
(6.redux的开发运维

(1.谷歌开发工具(原生)

yarn add redux-devtools-extension
  • 并且也需要在store.js中引入对应的扩展插件

  • // 引入 redux-devtools-extension
    import {composeWithDevTools} from 'redux-devtools-extension';
    
    // 引入 reducer , 帮助store处理操作
    export default  createStore(allReducers,composeWithDevTools(applyMiddleware(thunk)));
    
    

(2.优化代码

  1. 所有变量名命名规范,尽量触发对象的简写形式
  2. reducer.js中编写index.js用于汇总并暴露所有 的reducer

(3.本地查看build项目

yarn global add serve

​ serve -s build

6.Hooks的使用

react 16.8 以上才可以使用

(1.类式组件


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

class Example extends Component {
    constructor(props) {
        super(props);
        this.state = {count:0  }
    }
    render() { 
        return ( 
            <div>
                <p>You clicked {this.state.count} times</p>
                <button onClick={this.addCount.bind(this)}>Click me</button>
            </div>
         );
    }
    addCount(){
        this.setState({count:this.state.count+1})
    }
}
 
export default Example;

(2.函数式组件



import React, { useState } from 'react';
 
function Example(){
    const [count, setCount] = useState(0);   //解构赋值
    
    let _useState = userState(0)            // 真实的内部结构
    let count = _useState[0]
    let setState = _useState[1]  
    
    
    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={()=>{setCount(count+1)}}>Click me</button>
        </div>
    )
}
export default Example;
(1.useState路由状态

用于保存组件中的状态。功能同setState

import React,{useState} from 'react';
let [count,setCount] = useState(0);
    
setCount(count+1)           // 第一种写法:直接写出更新的状态值
setCount(count => count+1)  // 第二种写法:回调函数,是对象形式的简写
                            //  一般用于连续变化数据的时候使用。
(2.useEffect生命周期

处理功能

  1. 发送ajax请求获取数据
  2. 设置 订阅、启动定时器
  3. 手动惠以真实DOM

用于模拟生命周期

useEffect会在模板挂载到页面上,会自动执行。

  • componentWillMount , 可以添加[] 替换
  • componentWillUpdate 可以不添加第二个参数
  • componentWillUnmount 可以返回一个函数,在卸载之前执行。
useEffect(()=>{
    let timer = setInterval(()=>{
        setCount(count=>count+1)          
    },500)

    // 返回一个函数,在组件制裁之前执行,类似于WillUnmount
    return ()=>{
        clearInterval(timer)
    }
},[])
// 第二个参数用于监听数据。
// 传入第二个参数,类似DidUpdate, 监听指定的状态
// 不传入第二个参数,类似于DidMount,所有状态都不监听

const unmount = ()=>{
    ReactDom.unmountComponentAtNode(document.getElementById("root"))
}
  • 可以监听的数据: 相当于vue的watch
  1. props传递的数据‘
  2. location. 路由的变化
  3. 当前参数的变化。
  • 路由的变化, 只后于模块加载,所以可以用于监听
  • 对象的响应式,需要提前设计结构。
const [content,setContent] = useState({
      recommend:[],
    selfmusic:[],
      selflist:[]
});
  1. 监听路由 / 传递props
useEffect(()=>{                   // 改变路由
  getInitData();
  console.log("111",content)
},[obj.pathname])
useEffect(()=>{                    // 改变路由参数。
  getInitData();
  console.log("111",content)
},[props])
  1. 异步函数

const getInitData = async()=>{ // 可能没有效果,但用于axios有效
const sidebar = [title:‘111’]
setContent(sidebar)
}


(3.useRef绑定节点

可以在函数组件中存储、查找组件内的标签或者任意其它数据.

保存标签对象,功能与 React.createRef() 一样

import React,{useState,useEffect,useRef} from 'react';

const myRef = useRef(); // 声明一个ref变量,用于保存标签对象 

<input type="text"  ref={myRef}/>       // 在标签内使用ref绑定变量

console.log(myRef.current.value)         // 显示结果
(4.useMemo*函数优化

**作用:**用于优化组件之间的功能。

memo` 的用法是:`函数组件里面的PureComponent
优化之后的子组件
function Button({ name, children }) {
    // 执行函数
    function changeName(name) {
        console.log('11')
        return name + '改变name的方法'
    } 
    // 与useEffect功能类似。
    const otherName =  useMemo(()=>{
       return changeName(name)
    },[name])


  return (
      <>
        <div>{otherName}</div>
        <div>{children}</div>
      </>

  )
}
(5.useCallback*回调函数
// 页面加载后的回调函数。 (常用于验证码功能)
const handleClick = useCallback((captcha) => {
	console.log('captcha:', captcha);
}, []);
(6.useReducer(略)
(7.useContext(略)
(8.hooks中使用事件监听
// 监听对应节点的变化
useEffect(() => { 
      window.addEventListener("click", function() {
        console.log('222222222222226666');
     });  
    return () => {
        window.removeEventListener("click"); 
    };
  }, [window]);

不建议在页面中监听window事件, 毕竟,这会导致很大的性能浪费。

也不利于处理全局的window事件。

注意: 不能给一个空节点注册监听事件。

6.高级扩展内容

(1.Context (class)祖辈传值

组件之间的通信方式,一般用于祖先组件和后代组件。

  • 创建Context容器对象

  • const MyContext = React.createContext()
    const {Provider,Consumer} = MyContext;
    
  • 渲染组件时,外面包裹Provider,通过 value属性传递给后代组件数据

  • 
    <Provider value = {{username,age}}>
    	<B/>
    </Provider>
    
  • 后代读取数据

  • <Consumer>
    {
    	value => `${value.username} ,${value.age}`
    }
    </Consumer>
    
(2.PureComponent©性能优化

继承PureComponent,可以自动对组件内的数据进行比较,提高效率。

1.优化的来源

  1. 只要执行setState(),即使不改变状态数据,组件也会重新render() ==>效率低
  2. 只当前组件重新render(),就会自动重新render子组件,即使子组件没有用到父组件的任何数据==>效率低
  3. 目标:只有当组件的state或props数据发生改变时才重新render()
  4. 因为Component中的 shouldComponentuUpdate()总是返回true

2.生命周期优化

父组件优化: 只有状态改变时,才会更新,进行重新渲染。

shouldComponentUpdate(preProps,preState){
    console.log("现在的",this.props,this.state)
    console.log("要改变的",preProps,preState)

    if(this.state.Like === preState.Like){ // 如果状态没有发生改变,就不更新
        return false;
    }

    return true
}

子组件优化:父组件更新,只要子组件没有获得新的数据,就不会执行更新

shouldComponentUpdate(preProps,preState){
        // console.log("现在的",this.props,this.state)
        // console.log("要改变的",preProps,preState)

        if(this.props.Like === preProps.Like){ // 如果状态没有发生改变,就不更新
            return false;
        }

        return true
    }

3.继承PureComponent优化


class xxx  extednds PureComponent{  
    // PureComponent 使用浅比较,对象属性更新改变不会重新,会返回false
    let obj = this.state;
    obj.Like = "足球"
    this.setState(obj);
}
(3.ErrorBoundary©错误捕获

(1. 背景

错误边界是一种 React 组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI。

错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。

(2.问题

react 强调了只有上面 3 种实现下才会捕获错误。

无法自动捕获下面 4 种实现

  • 事件处理
  • 异步代码(例如 setTimeoutPromise回调函数)
  • 服务端渲染
  • 它自身抛出来的错误(并非它的子组件)

通常希望业务代码能够复用 ErrorBoundary 的错误处理逻辑。

(3.实现捕获

如果要 ErrorBoundary 能处理业务代码的自定义错误,只要在渲染期间抛出错误即可。

子组件获取数据错误 ,可以在父组件 使用 getDerivedStateFromError 获取错误信息,并把错误控制在一定的范围内。

  • 将错误控制在一定范围内。捕获后代的错误
  • 使用 getDerivedStateFromError 获取错误信息。
  • componentDidCatch用于记录错误数据。

class方法

//生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    //在render之前触发/返回新的state
    return {
        hasError: true,
    }
}
    
componentDidCatch(error,info) {
    // 统计页面的错误。发送请求发送到后台去
    console.log(error, info);
)

hook方法

function useErrorHandler() {
  const [error, setError] = React.useState(null);
  if (error != null) 
  	throw error;
  return setError;
}

function Foo() {
  const handleError = useErrorHandler();
  fetchMayError()
  	.catch(handleError);
  return <div></div>;
}
(4.组件间通信方式

1.组件间的关系∶

  • 父子组件
  • 兄弟组件(非嵌套组件)
  • 祖孙组件(跨级组件)

2.几种通信方式:

  1. props:
    i. children props

    ii. render props

  2. 消息订阅-发布:pubs-sub、event等等

  3. 集中式管理:redux、dva等等

  4. conText:生产者-消费者模式

3.比较好的搭配方式:

  1. 父子组件:props
  2. 兄弟组件:消息订阅-发布、集中式管理
  3. 祖孙组件(跨级组件)︰消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)

7.项目开发

(1.antd组件库
  1. 安装ant组件
npm install antd -S
  1. 使用ant组件

(1).antd普通组件

import 'antd/dist/antd.css';           // 引入样式(或者main.js中引入)
import { Button,DatePicker } from 'antd';          // 引入组件库


<Button type="primary">Primary Button</Button>
<DatePicker onChange={onChange}  />
<RangePicker picker="date" />

(2.ant-design/icons图标

安装模块

yarn add @ant-design/icons

使用图标

import { WechatOutlined } from '@ant-design/icons'; // 引入图标

// 查询到目标组件后,直接粘贴
<WechatOutlined />

(3). iconfont 阿里云组件

——main.js中引入css文件
import 'antd/dist/antd.css';

——新建立一个IconFont.jsx文件,添加以下内容
import { createFromIconfontCN } from '@ant-design/icons'; 
export const IconFont = createFromIconfontCN({
   scriptUrl: 'https://at.alicdn.com/t/font_3062734_0of12bnln1jr.js',
  //scriptUrl: '/font/iconfont.js',
});

——引入自定义组件 
import { IconFont } from 'iconfont/Iconfont';  

——使用iconfont组件
<IconFont type='icon-warning' />

如果下载到本地,也是类似操作.但需要把字体文件放在public文件夹下

把项目下载到本地,并放在public文件夹下,建立font文件夹夹

把引入文件路径修改为
import { createFromIconfontCN } from '@ant-design/icons'; 
export const IconFont = createFromIconfontCN({
  // scriptUrl: 'https://at.alicdn.com/t/font_3062734_0of12bnln1jr.js',
  scriptUrl: '/font/iconfont.js',
});

3.按需引入 (暂不考虑)

需要对create-react-app脚手架进行配置

1. 安装模块
npm install react-app-rewired customize-cra

2.修改 package.json 
 "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
  },
  
3.安装模块
npm install babel-plugin-import

4. 项目下新建立 config-overrides.js
const {override, fixBabelImports} = require('customizecra');

module.exports = override(
    fixBabelImports('import', {
        libraryName: 'antd',
        libraryDirectory: 'es',
        style: 'css',
    }),
);

暂时修改不了主题色

【React精品】React全家桶+完整商城后台项目#UniJS#Antd Pro#全程实录(已完结)_哔哩哔哩_bilibili

(3.zarm组件库(略)

(1.安装模块

yarn add zarm

(2.使用组件

(1).zarn普通组件

——在main.js中引入css
import 'zarm/dist/zarm.css';      // 引入css (或者在main.js中引入)

——使用组件
import {Button } from 'zarm';
<Button theme="primary">Hello World!</Button>

(4.customize-cra根目录
  • 方法一: 初始化项目后使用命令yarn eject(不建议)

只有在开始建立项目的时候, 运行 **yarn eject ** 或者 npm run eject, 就会在项目下生成config文件夹,

该文件下有一个webpack.config.js 文件 , 向 alias 选项中添加 '@': path.resolve('src'),

然后,可以在项目下使用 @ 表示 根路径

如:

import Hello from '@/First/Second'

注意:单独复制config无效, 已经有自定义文件的项目无法使用命令。

如果在gitee的本地仓库中,可能需要先提交,再使用命令。

  • 方法二:通过安装customize-cra插件来实现(建议)

当方法一失效或者不利于重新建立项目 时,可以采用此方法。

安装模块:
	yarn add custom-cra react-app-rewired
  1. 在根目录新建config-overrides.js, 与package.json同级
const {
  override,
  addWebpackAlias
} = require('customize-cra')
const path = require('path')

module.exports = override(
  // 路径别名
  addWebpackAlias({
    '@': path.resolve(__dirname, 'src'),
    '@c': path.resolve(__dirname, 'src/components'),
    '@views': path.resolve(__dirname, 'src/views'),
    '@utils': path.resolve('src/utils'),
    '@styles': path.resolve('src/styles'),
  })
) 
  1. 修改package.json文件
 原本之前的
"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
修改之后
scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  }, 
  1. 这边保存重启一下即可

注意:路由懒加载必需使用相对路径,当前设置的根路径无效

(5.react-transition-group
 yarn add react-transition-group@4.1.1  
 

功能相当于使用animation

import {CSS}



                <CSSTransition in={this.state.isShow}
                    timeout={2000}
                    classNames="boss-text"
                    unmountOnExit           //  删除节点
                >
                    <div>大魔王</div>
                </CSSTransition>
                
 
.boss-text-enter{opacity:0;color:blue}   /*从隐藏到显示*/
.boss-text-enter-active{opacity:1; transition:opacity 2000ms;} 
.boss-text-enter-done{opacity:1; } 

.boss-text-exit{opacity:1; color:red}     /*从显示到退出*/
.boss-text-eixt-active{opacity:0; transition:opacity 2000ms;} 
.boss-text-eixt-done{opacity:0;}                 
                
                

控制多个模块(包括新生成的)

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

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

8.项目上线

(1.项目根目录
  • https://localhost:3000/css/xx
  • /css
  • /%PUBLIC_STAIC%/css

注意: 开发者模式下,css是以内联样式的形式加载的。

生产模式下,css才会打包成单独的css文件

原react-scripts/ webpack.config.js内容
      isEnvDevelopment && require.resolve('style-loader'),
      isEnvProduction && {
        loader: MiniCssExtractPlugin.loader,
        // css is located in `static/css`, use '../../' to locate index.html folder
        // in production `paths.publicUrlOrPath` can be a relative path
        options: paths.publicUrlOrPath.startsWith('.')
          ? { publicPath: '../../' }
          : {},  
      },
(2.项目布署
yarn start          测试项目
yarn build          打包项目

本地使用,可能不会出现异常,但会出现警告。服务器上遇到无法运行,则可以使用以下方案。

注意:如果出现**[react-scripts: command not found after running npm start**,则考虑使用npm。

  • 先删除原有的 package-lock.json 和 node_modules

  • 使用以下命令

  • npm install                 安装模块
    npm run start               测试项目     // 在服务器上依旧无法访问网站
    npm run build               打包项目
    
  • 正常情况下,打包问题可以完美解决,但运行测试还是不行。

最后,注意一下,react是否会出现与vue一样的路由问题,即刷新导致页面丢失的问题

location / {
        index  /www/wwwadmin/myblog-project/blog_react/build/index.html;   ### 首页
        try_files $uri $uri/ /index.html;          
        ### 将页面重定向index.html, 将路由交给前端处理。
}

运行结果: session会话设置失效,导致登录模块失效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值