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:
      
      小张--- 18 (转成真实DOM)
      小李--- 18 (不做更改)
      小王--- 19
      (不做更改)
    • 更新后数据虚拟DOM:
      
      小张—--18 (转成真实DOM)
      小李---18 (转成真实DOM)
      小王--- 19
      (转成真实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.
   
   
   
    //  通过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的模板语法
    
2.两种创建虚拟DOM的方式(jsx , js 两种方式)

5.jsx 与模块化

(1. 什么是jsx?

1.基本概念

  1. 全称:JavaScript XML

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

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

    (1.写法: var ele =

    Hello Jsx!

    (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早期用于存储和传输数据

      
           LIli>
           17
      

(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.函数式组件
  
(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类

(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);
 };
 

(2.回调函数的ref

什么是回调函数?

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



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


// 2.绑定类内定义的函数作为回调函数
saveinput = (c)=>{         
    this.input1 = c;      
};


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);    
};


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 ( 
            
用户名:
密码:
(2.非受控组件(ref)

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

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

(2.常用实例

// 一般用于获取绑定对象的DOM的子类
    {this.ul=event}}> ...
通过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 ( 
            
用户名:this.username = c} type="text" />
密码:this.password = c} type="password" />
(3.高阶函数和函数柯里化

(1.高阶函数

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

  1. 返回值是一个函数

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

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


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

render(){
    return ( 
    
用户名:
密码:

(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 ( 
        
用户名: this.saveFormData("username",event.target.value)} type="text" />
密码: this.saveFormData("password",event.target.value)} type="password" />

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 (
            

当前求和为:{this.state.count}

) } }

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

只有在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 (
            

我是A组件

) } } 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 (

我是B组件

我的车是:{this.props.carName}

) } } // 2.渲染组件到页面 ReactDOM.render(,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. 状态数据,不能直接修改或更新


     

12.html标签解析

1.dangerouslySetInnerHTML解析

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

  • 2.其它知识

    1.label友好型标签,for里面填写input的id值,点击label的时候,就会解锁focus与datalist很类似
        
        
        
    2.input标签中的checked 如果需要设置变量,则需要把checked改成 defaultChecked 
                       
    

    3.vscode快捷键

    Snippet Renders
    imr Import React
    imrc Import React / Component
    imrd Import ReactDOM
    imrs Import React / useState
    imrse Import React / useState useEffect
    impt Import PropTypes
    impc Import React / PureComponent
    cc Class Component
    ccc Class Component With Constructor
    cpc Class Pure Component
    fc Function Component
    cdm componentDidMount
    uef useEffect Hook
    cwm componentWillMount
    cwrp componentWillReceiveProps
    gds getDerivedStateFromProps
    scu shouldComponentUpdate
    cwu componentWillUpdate
    cdu componentDidUpdate
    cwu componentWillUpdate
    cdc componentDidCatch
    gsbu getSnapshotBeforeUpdate
    ss setState
    ssf Functional setState
    usf Declare a new state variable using State Hook
    ren render
    rprop Render Prop
    hoc Higher Order Component

    13.props的基本使用

    (1.传递props属性,  标签属性
    
  • 姓名:{this.props.name}
  • ReactDOM.render(,document.getElementById("test2"))


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

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

    
    
    (2.引入img图片
    图片需要单独作为变量引入。
    import Logo from '../../assets/images/user.jpg' 
    
       
    

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

    {movieObj.title}
    

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

    在index.html头部添加
    
     
    

    其它加速网站

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

    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 [email protected]
    

    注意:一定要把版本降到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接收

      // 父组件
      
       
       // 子组件
      const {todos} = this.props;
    

    2.子传父

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

     // 父组件
     
    // 子组件 // 调用父组件传递的函数,将输入的数据通过函数参数传递给父组件 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 [email protected]
    
    最新版本有问题,所以确定一下路由版本
    

    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';
    
    // 路由链接
    首页
    
    (2.Route路由规则

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

    import {Route} from 'react-router-dom';
    //绑定路由或者注册路由
    
    
    
    (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 类名*/}
    首页 |
    
    {/*也可以自定义链接, 使用activeClassName来显示活动时的类名*/}
    首页 | 
    首页 |
    

    4.封装NavLink组件

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

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

    ---- app.js组件
    
        {/*标签体的内容是一个特殊的属性children*/}
        Home
        about
    
    
    
    ----自定义MyNavLink组件 
        // 可以动态向子组件传入属性属性
        // NavLink 会被转化成超链接a标签,  children属性值会自动填充到NavLink标签体中
        
    
    
    
    (5.Switch单路由匹配机制
    • 一个路由规则,对应多个组件。 这样会导致严重的效率问题

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

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

    import {Switch} from 'react-router-dom';
    
     
           
           
           
           {/*编写路由规则 或者 注册路由*/}
    
    
    
    (6.DocumentTitle标题模块

    安装模块

    yarn add react-document-title
    

    使用模块

    
    import  DocumentTitle from 'react-document-title' 
    
    
    Loading}>
         {
         	routerMap.map((item,index)=>{
                return ( 
                        			 )
             })
           }
    
    
    
    // 对当前的总页面进行设置
    function Home(){ 
        return (
            
                 ....
            
        )
    } 
    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'))     // 懒加载
      
      Loading}>
              
              
      
      
      
    (9.Redirect路由重定向
    • 当所有内部都没有匹配到结果的时候,就会重定向目标路由。
    • 一般放在路由规则的底部
    import {Redirect, Link} from 'react-router-dom'
    
    1.标签式重定向
    
       
       
    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);
        }
    
    

    事件触发

                                        
                                        
                                        
                // 不传入参数,可以不使用回调函数
    
                              
    
    

    区别: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精确匹配

    模糊匹配:

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

    默认模糊匹配

    
       about
       
    

    精确匹配:

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

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

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

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

       
    
    (2.嵌套路由(二级)
    • 二级路由必须先匹配到上一级路由。
    • 二级路由是层层嵌套,上一级使用精确匹配会导致无法匹配二级路由。
      
    news Message
    {/*注册路由*/}
    (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},
    ]
    
    
    
      { routeConfig.map((item,index)=>{ return (
    • {item.title} >) }) }
    { {/*router-view*/} routeConfig.map((item,index)=>{ return () }) }
    (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 */} //

    66666

    //

    Hello World

    //
    {/*只能被 一个单独的标签包裹div */}

    66666

    Hello World

    ) } } export default App;

    挂载DOM

    import React from 'react';
    import ReactDOM from 'react-dom';
    
    
    ReactDOM.render( 
       ,
      document.getElementById('root')
    );
    
    
    
    (14.路由跳转传参
    (1.params路径参数
    • 路由规则使用 :id 或者 :xx
    • 路由链接,可以使用“+” 连接路径 ,但需要使用{} 包围。 (一般数字是要{}包围的)
    • 目标路由组件,可以使用this.props.match.params.xxx 获取传入的参数
     {/*路由链接*/}
     

    {item.title}

    {/*

    {item.title}

    */} {/*注册路由*/} {/*目标路由组件*/} 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;
    
     {item.title}
    
    // 定义路由规则,  主路由需要 包围
    
    
    // 可以在加载模板后获取
       componentDidMount(){
            console.log(this.props)
            let tempId = this.props.match.params.id;
            this.setState({id:tempId})      // 把数据放在state里面
        }
         
    

    List-page:{this.state.id}

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

      // vue
        
  • 文章二
  • // react

    作者编号{this.state.author_id}

      { /**这其实就像v-for */} { this.state.list.map((item,index)=>{ return (
    • {/*一定要传入key*/} {item.title}
    • ) }) }

    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})
     
        }
    
        

    计算结果为:{store.getState()}

    store优化:全局监听redux

    
    // 监听redux,当state改变时,重新渲染组件。
    store.subscribe(()=>{
        ReactDOM.render(
            ,
            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对象

    
    

    容器组件: 定义一个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'
    
     
    	
    ,
    
    (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 ( 
                

    You clicked {this.state.count} times

    ); } 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 (
            

    You clicked {count} times

    ) } 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变量,用于保存标签对象 
    
           // 在标签内使用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 (
          <>
            
    {otherName}
    {children}
    ) }
    (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属性传递给后代组件数据

    • 
      
      	
      
      
    • 后代读取数据

    • 
      {
      	value => `${value.username} ,${value.age}`
      }
      
      
    (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 
    ; }
    (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';          // 引入组件库
    
    
    
    
    
    
    
    (2.ant-design/icons图标

    安装模块

    yarn add @ant-design/icons
    

    使用图标

    import { WechatOutlined } from '@ant-design/icons'; // 引入图标
    
    // 查询到目标组件后,直接粘贴
    
    

    (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组件
    
    
    

    如果下载到本地,也是类似操作.但需要把字体文件放在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';
    
    
    
    (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 [email protected]  
     
    

    功能相当于使用animation

    import {CSS}
    
    
    
                    
                        
    大魔王
    .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'
    
    
      {this.ul=ul}}> { this.state.list.map((item,index)=>{ return ( ) }) }

    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会话设置失效,导致登录模块失效。

    你可能感兴趣的:(web前端学习,前端框架,react,javascript)