React的学习笔记-(Bilibili天禹老师)

news2024/12/22 20:07:00

React的特点

  • 采用组件化模式,声明式编码,提高开发效率和组件复用率
  • React Native中可以使用React语法进行移动端开发(IOS和Android)
  • 使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互

babel用处

  • es6 => es5
  • jsx => js

1.你好,react

  • 注意引入顺序
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>首次React</title>
</head>
<body>
  <div id="test"></div>
  <!-- 引入react核心库-核心库必须要第一个引入 引入后会有一个全局对象React-->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- 引入react操作DOM的库 引入后会有一个全局对象ReactDom-->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,使其可以书写jsx语法(jsx语法转化为js语法) -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 书写jsx语法,注意type的值 -->
  <script type="text/babel">
    
    //1.创建一个虚拟的DOM
    	//不需要添加引号
    const VDOM = <h1>你好,react</h1>
    //2.使用react语法将虚拟的DOM转化为真实的DOM,插入页面
    ReactDOM.render(VDOM,document.getElementById("test"));
  </script>
</body>
</html>

效果

二种创建虚拟DOM的方式

方式1-使用jsx的形式

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>虚拟DOM的二种创建方式</title>
</head>
<body>
  <div id="test"></div>
  <!-- 引入react核心库-核心库必须要第一个引入 引入后会有一个全局对象React-->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- 引入react操作DOM的库 引入后会有一个全局对象ReactDom-->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,使其可以书写jsx语法(jsx语法转化为js语法) -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 书写jsx语法,注意type的值 -->
  <script type="text/babel">
    
    //1.创建一个虚拟的DOM
    const VDOM = <h1 id='content'><span>你好,React,世界我来了</span></h1>
    //2.使用react语法将虚拟的DOM转化为真实的DOM,插入页面
    ReactDOM.render(VDOM,document.getElementById("test"));
  </script>
</body>
</html>

使用jsx的形式

方式2-使用javascript形式

  • 主要是利用引入React库所当中的React.createElement创建虚拟DOM
  • React.createElement语法参数
    • 第一个参数:为创建的标签名称
    • 第二个参数:为标签的属性
    • 第三个属性:为标签的内容
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>虚拟DOM的二种创建方式</title>
</head>
<body>
  <div id="test"></div>
  <!-- 引入react核心库-核心库必须要第一个引入 引入后会有一个全局对象React-->
  <script type="text/javascript" src="../js/react.development.js"></script>
  <!-- 引入react操作DOM的库 引入后会有一个全局对象ReactDom-->
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <!-- 引入babel,使其可以书写jsx语法(jsx语法转化为js语法) -->
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <!-- 书写jsx语法,注意type的值 -->
  <script type="text/babel">
    // <h1 id='content'><span>你好,React,世界我来了</span></h1>
    //1.创建一个虚拟的DOM
      //第一个参数为创建的标签名称
      //第二个参数为标签的属性
      //第三个参数为标签的内容
    const VDOM = React.createElement('h1',{id:'content',className:'content-show'},React.createElement('span',{},'你好,React,世界我来了'));
    //2.使用react语法将虚拟的DOM转化为真实的DOM,插入页面
    ReactDOM.render(VDOM,document.getElementById("test"));
  </script>
</body>
</html>

虚拟DOM和真实DOM

  • 虚拟DOM输出查看

虚拟DOM

  • 真实DOM输出查看

真实DOM

  • 总结
    • 虚拟DOM的本质其实就是Object类型的一般对象
    • 虚拟DOM比较’轻’,真实DOM比较’重’
      • 轻,重体现在这个对象的所具有的属性和方法上

jsx的语法规则

  1. 若第一层存在多个标签,则必须要有一个统一的标签,否者就报错(也就是根标签只有一个)
  2. 书写js表达式代码,需要以{ }开头,用于区分普通的字符代码
  3. 如果需要书写内联样式,需要以{ {...} } ,第一个花括号是用来区分普通的字符代码的,代表要书写js代码,其中,{...}是对象,并且要以小驼峰的形式命名key
  4. 书写类名要以 className 书写
  5. 标签必须要闭合,否者会报错
  6. 创建虚拟DOM的时候,不需要引号
  7. 在jsx中,标签名是可以随意书写的
    1. 如果是[小写]的,react则会将当前的jsx标签对应为一个html标签,若对应成了,直接渲染展示,否则的话就报错
    2. 如果是[大写]的,react则会将当前的jsx标签对应为一个组件,去查找组件的定义位置,若找到了,直接渲染展示,否者就报错
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>JSX语法规则</title>
  </head>
  <body>
    <div id="test"></div>
    <!-- 引入react核心库-核心库必须要第一个引入 引入后会有一个全局对象React-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react操作DOM的库 引入后会有一个全局对象ReactDom-->
    <script
      type="text/javascript"
      src="../js/react-dom.development.js"
    ></script>
    <!-- 引入babel,使其可以书写jsx语法(jsx语法转化为js语法) -->
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 书写jsx语法,注意type的值 -->
    <script type="text/babel">
      const id = 'cOnTEnt';
      const text = "hEllo,你好,I aM cOming!";
      //1.创建一个虚拟的DOM
      const VDOM = (
        <div>
          <h1 id={id.toLowerCase()} className='biubiu'>
            <span style={{backgroundColor:'red',color:'blue'}}>{text.toUpperCase()}</span>
          </h1>
          <h1>你好</h1>
        </div>
      );
      //语法规则
      /*     
        1.若第一层存在多个标签,则必须要有一个统一的标签,否者就报错(也就是根标签只有一个)
        2.书写js表达式代码,需要以 { } 开头,用于区分普通的字符代码
        3.如果需要书写内联样式,需要以{ {...} } 其中,{...}是对象,并且要以小驼峰的形式命名
        4.书写类名要以 className 书写
        5.标签必须要闭合,否者会报错
        6.创建虚拟DOM的时候,不需要引号
        7.在jsx中,标签名是可以随意书写的
          7.1,如果是[小写]的,react则会将当前的jsx标签对应为一个html标签,若对应成了,直接渲染展示,否则的话就报错
          7.2,如果是[大写]的,react则会将当前的jsx标签对应为一个组件,去查找组件的定义位置,若找到了,直接渲染展示,否者就报错
        */
      //2.使用react语法将虚拟的DOM转化为真实的DOM,插入页面
      ReactDOM.render(VDOM, document.getElementById("test"));
    </script>
  </body>
</html>

表达式和语句(代码)的区别

  • 一句话概括就是表达式左侧是可以有值返回的,而语句是没有的
  • 所以不管是在React还是Vue当中,插值语法都是只能书写表达式的

表达式

  • 表达式就是会产生结果的值,可以放在React任何一个需要值的地方
  • 下面这些都是表达式
    • a
    • a+b
    • demo(1) (函数返回值)
    • function test() { }

语句(代码)

  • 语句是不会产生结果的
  • 下面这些都是语句(代码)
    • if( ){ }
    • for( ) { }
    • switch( ) {case: xxx}

JSX小练习

  <body>
    <div id="test"></div>
    <!-- 引入react核心库-核心库必须要第一个引入 引入后会有一个全局对象React-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react操作DOM的库 引入后会有一个全局对象ReactDom-->
    <script
      type="text/javascript"
      src="../js/react-dom.development.js"
    ></script>
    <!-- 引入babel,使其可以书写jsx语法(jsx语法转化为js语法) -->
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 书写jsx语法,注意type的值 -->
    <script type="text/babel">
      const arrayData = ['React','Vue','Angulay'];

      const VDOM = (
        <div>
          <h1>前端JS框架列表</h1>
          <ul>
            {arrayData.map((item,index) => (<li key={index}>{item}</li>))}
          </ul>
        </div>
      )
      ReactDOM.render(VDOM,document.getElementById('test'));
    </script>
  </body>

React函数式组件

  • Reace有函数式组件,也有类似组件,这里做下函数式组件的笔记
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>1_函数式组件</title>
</head>
<body>
  <div id="test"></div>
  <script type="text/javascript" src="../js/react.development.js"></script>
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <script type="text/babel">
    //1.函数式组件
    function Demo(){
      //此处的this是undefined,是因为经过babel编译后,开启了严格模式
      console.log(this);
      return <h1>我是函数定义的组件,适用于简单组件的定义</h1>
    }

    ReactDOM.render(<Demo/>,document.getElementById('test'));

  </script>
</body>
</html>
  • 执行ReactDOM.render发生了什么?
    • 1.React发现<MyComponent/>标签,去寻找MyComponent组件定义的位置,发现是函数式组件
    • 2.是函数式组件,则react调用该函数
    • 3.react调用该函数,函数会返回一个虚拟的DOM,并由react转换为真实的DOM,并显示在页面上

React类式组件

  • 什么是类式组件,就是通过类来创建组件
  <div id="test"></div>
  <script type="text/javascript" src="../js/react.development.js"></script>
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <script type="text/babel">
    //1.类式组件
      //继承react内置的类
    class MyComponent extends React.Component {
      
      render(){
        console.log(this);//为创建的MyComponent实例对象
        return <h2>我是用类定义的组件</h2>
      }
    }

    ReactDOM.render(<MyComponent/>,document.getElementById('test'));

  </script>
  • render方法,是给react渲染时候调用的
  • 执行ReactDOM.render发生了什么?
    • 1.React发现<MyComponent/>标签,去寻找MyComponent组件定义的位置,发现是类式组件
    • 2.是类式组件,则react创建该类的实例对象
    • 3.react调用该类的实例对象身上的render方法,render方法返回一个虚拟的DOM,并由react转换为真实的DOM,并显示在页面上

组件的三大属性之state

在学之前,先来复习下this的指向

  • 严格模式,是禁止this指向window的
function demo1(){
    console.log("this指向:",this);//window
}
demo1();

function demo(){
    "use strict"
    console.log("this指向:",this);//undefined
}
demo();
  • 严格模式下,this禁止指向window,所以下面这个例子中,thisundefined
<script>
    "use strict";
    function DemoWatch() {
        console.log(this);
    }
    DemoWatch();//输出 'undefined'
</script>
  • 普通模式下(除类外,并且非箭头),this指向执行者(刽子手~)
<script>
function DemoWatch(){
	console.log(this);
}
window.DemoWatch();//输出 'window'
</script>
  • 普通模式下(除类外,箭头函数下),this指向上层作用域下的this
var obj = {
fn1:() => {
	console.log(this);
},
fn2:function(){
    console.log(this);
    }
}
obj.fn1(); //输出 window
obj.fn2(); //输出 obj
    • 先来想一下这个this指向哪里

        <script type="text/javascript">
          class Demo {
            show(){
              console.log("看看this在哪里",this);
            }
          }
          const example = new Demo();
      
          example.show(); //输出结果为?
      
          const x = example.show;
      
          x(); //输出结果为?
      
        </script>
      
    • 答案依次为

      • example实例对象
        • 因为此时的show方法是由example实例化对象所调用的
      • undefined
        • 此时show方法是由window调用的,如果没有开启严格模式,那么应该是输出window,但是类中的方法局部默认开启了严格模式,所以禁止指向window,所以就输出了undefined

state

  • 未改变state当中的状态
<body>
  <div id="test"></div>
  <script type="text/javascript" src="../js/react.development.js"></script>
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <div id="test"></div>
  <script type="text/babel">

    class Weather extends React.Component {

      constructor(props){
        super(props);
        this.state = {
          isHot:false,
        }
      }

      changeWeather(){
      	//这里的this不是Weather的实例对象,因为不是通过Weather的实例对象调用的
      	//而是作为点击的回调调用的
     	//由于开启了严格模式,本该指向window的对象的不被允许,所以被指向了undefined
        console.log("我被单击了",this);
      }

      render(){
        //在这里定义虽然也是可以的
        //但是不推荐,因为晚了一步,我们其实应该在创建好实例对象就定义,而不是创建好实例化对象后调用render方法后才定义
        // this.state = {isHot:false};
        return <h2 onClick={this.changeWeather}>今天的天气,{this.state.isHot ? '热' : '凉快'}!</h2>
      }

    }

    ReactDOM.render(<Weather/>,document.getElementById('test'));

  </script>
</body>
  • 改变state状态的写法
    • 通过继承的React.Component身上的方法setState来完成响应式操作(数据变了,视图也变)
      • setState是一个同步的方法,但是后续更新状态的动作是异步的
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>state属性</title>
</head>
<body>
  <div id="test"></div>
  <script type="text/javascript" src="../js/react.development.js"></script>
  <script type="text/javascript" src="../js/react-dom.development.js"></script>
  <script type="text/javascript" src="../js/babel.min.js"></script>
  <div id="test"></div>
  <script type="text/babel">

    class Weather extends React.Component {

      constructor(props){
        super(props);
        this.state = {
          isHot:false,
          wind:'微风',
        }
        // 解决 changeWeather 当中this指向的问题
          //this.changeWeather属性添加到了每一个实例身上
        this.changeWeather = this.changeWeather.bind(this);
      }

      //此方法在Weather原型链上
      changeWeather(){
        //改变state当中的属性
        this.setState({
          //取反
          isHot:!this.state.isHot,
        });
        //此操作为覆盖,不是替换,等同于下面这代码
        // this.setState({
        //   ...this.state,
        //   isHot:!this.state.isHot,
        // })
      }

      render(){
        //在这里定义虽然也是可以的
        //但是不推荐,因为晚了一步,我们其实应该在创建好实例对象就定义,而不是创建好实例化对象后调用render方法后才定义
        // this.state = {isHot:false};
        return <h2 onClick={this.changeWeather}>今天的天气,{this.state.isHot ? '热' : '凉快'}!</h2>
      }

    }

    ReactDOM.render(<Weather/>,document.getElementById('test'));

  </script>
</body>
</html>
  • 调用次数
    • 构造器调用次数: 使用组件多少次,就调用多少次

state的简写形式-解决this指向问题

  • 使用到了箭头函数
  • 使用到了class在原型链上定义属性
<body>
  <div id="app"></div>
  <script src="../js/react.development.js"></script>
  <script src="../js/react-dom.development.js"></script>
  <script src="../js/babel.min.js"></script>
  <script type="text/babel">
    class Weather extends React.Component{
      state = {
        hot:true,
        other:'多云'
      }
      constructor(props,name){
        super(props)
        this.name = name;
      }
      //相当于在原型链上创建一个属性(方法)
      changeHot = ()=>{
        this.setState({
          hot:!this.state.hot
        })
      }
      render(){
        return (<h2 onClick={this.changeHot}>今天的天气真{this.state.hot?'热':'凉快'},{this.state.other}</h2>)
      }
    }
    ReactDOM.render(<Weather/>,document.getElementById("app"));
  </script>
</body>

组件的三大属性之props

  • 注意在组件当中的扩展运算符的使用
    • 这里的扩展运算符并不是js原生的{...info},而是babel+react环境就可以让展开运算符展开一个对象,但是仅仅适用于传递标签属性
<body>
  <div id="app"></div>
  <div id="app2"></div>
  <script src="../js/react.development.js"></script>
  <script src="../js/react-dom.development.js"></script>
  <script src="../js/babel.min.js"></script>
  <script type="text/babel">
    class UserInfo extends React.Component {
      
      render(){
        const {name,age,sex} = this.props;
        return (
          <ul>
            <li>姓名:{name}</li>  
            <li>性别:{age}</li>  
            <li>年龄:{sex}</li>  
          </ul>
        )
      }
    }

    ReactDOM.render(<UserInfo name='李白' age='18' sex='男'/>,document.getElementById('app'));
    const info = {
      name:'李白',
      age:'18',
      sex:'男',
    }
    //这里的...info,并不是js原生的{...info},而是babel+react环境就可以让展开运算符展开一个对象,但是仅仅适用于传递标签属性
    ReactDOM.render(<UserInfo {...info}/>,document.getElementById('app2'));

  </script>
</body>

对props进行约束-类式组件

  1. 引入约束文件
 <script src="../js/prop-types.js"></script>
  1. 使用约束
  • 通过在类身上添加属性propTypes,结合约束文件当中的PropTypes对象当中的类型来使用
<body>
  <div id="app"></div>
  <script src="../js/react.development.js"></script>
  <script src="../js/react-dom.development.js"></script>
  <script src="../js/babel.min.js"></script>
  <script src="../js/prop-types.js"></script>
  <script type="text/babel">
    class Person extends React.Component{
      //此为实例属性,添加在每一个实例化对象身上,通过实例对象.属性名调用
      state = {
        other:'大家好'
      }
      //此为静态属性,添加在类身上,通过类名.属性名调用
      static propTypes = {
        name:PropTypes.string,//限制name属性必须为字符串
        age:PropTypes.number,//限制age属性必须为数字
        sex:PropTypes.string,//限制sex属性必须为字符串
        address:PropTypes.string,//限制address属性必须为字符串
      }
      render(){
        const {name,age,sex,address } = this.props;
        return (
          <ul>
            <li>姓名:{name}</li>
            <li>年龄:{age+1}</li>
            <li>性别:{sex}</li>
            <li>住址:{address}</li>
          </ul>
        )
      }
    }
    ReactDOM.render(<Person name='李白' age={18} sex='男' address='地球村'/>,document.getElementById("app"));
  </script>
</body>

  1. 当接收到一个不符合的值的时候,控制台会报警告,但是不会影响视图的渲染

Warning: Failed prop type: Invalid prop 'age' of type 'string' supplied to 'Person', expected 'number'.

对props进行约束-函数式组件

  • 函数式组件当中,props是唯一可以使用的三大属性
  • 添加约束的方法和类差不多,也是向函数添加属性propTypes即可
<body>
  <div id="app"></div>
  <script src="../js/react.development.js"></script>
  <script src="../js/react-dom.development.js"></script>
  <script src="../js/babel.min.js"></script>
  <script src="../js/prop-types.js"></script>
  <script type="text/babel">

    function Person(props){
      const {name,age,sex,address } = props;
      return (
          <ul>
            <li>姓名:{name}</li>
            <li>年龄:{age+1}</li>
            <li>性别:{sex}</li>
            <li>住址:{address}</li>
          </ul>
        )
    } 
    //添加约束
    Person.propTypes = {
      name:PropTypes.string,
      age:PropTypes.number,
      sex:PropTypes.string,
      address:PropTypes.string,
    }

    //弹出警告
    // ReactDOM.render(<Person name='李白' age='18' sex='男' address='地球村'/>,document.getElementById("app"));
    ReactDOM.render(<Person name='李白' age={18} sex='男' address='地球村'/>,document.getElementById("app"));
  </script>
</body>

ref

  • 通过React当中的ref,可以获取指定节点,获取的节点为真实DOM

字符串形式的ref

  • 17.x版本已经不推荐使用了,这里做一个学习记录

  • 原理就是通过为元素添加ref属性后,会在实例对象身上的ref属性上添加对应的节点,如下图,就是添加了二个refinput身上,输出this就可以看到ref属性下的二个节点

image-20221009222127086

  • 具体例子
<body>
  <div id="app"></div>
  <script src="../js/react.development.js"></script>
  <script src="../js/react-dom.development.js"></script>
  <script src="../js/babel.min.js"></script>
  <script type="text/babel">

    class Demo extends React.Component {

      showData1 = () => {
        //之前的做法
        // const val = document.getElementById('input1').value;
        const {input1} = this.refs;
        alert(input1.value);
      }

      showData2 = () => {
        const {input2} = this.refs;
        alert(input2.value);
      }


      render(){
        return (
          <div>
            <input ref='input1' type='text' placeholder='点击按钮提示输入'/>  

            <button onClick={this.showData1}>点我提示数据</button>

            <input ref='input2' onBlur={this.showData2} type='text' placeholder='失去焦点提示输入'/>  
            
          </div>
        )
      }
    }
    ReactDOM.render(<Demo/>,document.getElementById('app'));
  </script>
</body>

回调形式的ref

  • 格式

    • <input ref={currentNode = this.input1 = currentNode}/>
    • 其中,ref里面的为一个函数,React调用后会传入一个参数(这个参数就是这个ref所指向的节点),由currentNode接收后,通过this.input1挂载到了当前实例对象身上
  • 不过这种方式的ref也不推荐了…

  • 示例

<body>
  <div id="app"></div>
  <script src="../js/react.development.js"></script>
  <script src="../js/react-dom.development.js"></script>
  <script src="../js/babel.min.js"></script>
  <script type="text/babel">

    class Demo extends React.Component {

      showData1 = () => {
        //之前的做法
        // const val = document.getElementById('input1').value;
        const {input1} = this;
        alert(input1.value);
      }

      showData2 = () => {
        const {input2} = this;
        alert(input2.value);
      }


      render(){
        return (
          <div>
            <input ref={currentNode => this.input1 = currentNode} type='text' placeholder='点击按钮提示输入'/>  

            <button onClick={this.showData1}>点我提示数据</button>

            <input ref={currentNode => this.input2 = currentNode} onBlur={this.showData2} type='text' placeholder='失去焦点提示输入'/>  
            
          </div>
        )
      }
    }
    ReactDOM.render(<Demo/>,document.getElementById('app'));
  </script>
</body>

createRef的使用

  • React为了避免this身上太多的属性,就推出了createRef
  • createRef的使用如下
    • 实例对象添加container1属性(只可存储一个节点数据),值设置为React.createRef()
    • 在需要获取节点的身上添加ref属性,值为{this.container1}
    • 输出查看获取的节点的值this.container1.current.value
  • 输出查看container1

  • 示例
<body>
  <div id="app"></div>
  <script src="../js/react.development.js"></script>
  <script src="../js/react-dom.development.js"></script>
  <script src="../js/babel.min.js"></script>
  <script type="text/babel">

    class Demo extends React.Component {
      
      input1 = React.createRef();
      input2 = React.createRef();

      showData1 = () => {
        console.log(this.input1);
        console.log(this.input1.current.value);
      }

      showData2 = () => {
        console.log(this.input2);
        console.log(this.input2.current.value);
      }


      render(){
        return (
          <div>
            <input ref={this.input1} type='text' placeholder='点击按钮提示输入'/>  

            <button onClick={this.showData1}>点我提示数据</button>

            <input ref={this.input2} onBlur={this.showData2} type='text' placeholder='失去焦点提示输入'/>  
            
          </div>
        )
      }
    }
    ReactDOM.render(<Demo/>,document.getElementById('app'));
  </script>
</body>

React当中为什么要简历一个onClick,onXxx的事件

  • 我们知道,在原生js当中,是有事件onclick的,那么为什么React需要创建一个onClick来绑定事件呢?

  • 原因

    • React使用的自定义事件(onClick,onBlur等),而不是使用原生DOM事件,是为了更好的兼容性
    • React的事件是通过事件委派的方式处理的,委托给组件最外层的元素,是为了提高效率
      • 所以你不必担心在li等元素身上绑定自定义事件太多而会出现执行效率问题,React会自动将他们处理为事件委派的形式
  • 其他

    • 在自定义事件当中,可以通过event.target得到发生事件的DOM元素对象

    • 示例

      <body>
        <div id="app"></div>
        <script src="../js/react.development.js"></script>
        <script src="../js/react-dom.development.js"></script>
        <script src="../js/babel.min.js"></script>
        <script type="text/babel">
      
          class Demo extends React.Component {
      
            sayHello = (event) => {
              console.log(event.target);
            }
      
            render(){
              return (
                <div>
                  <button onClick={this.sayHello}>点我输出发生事件的DOM元素</button>
                </div>
              )
            }
          }
          ReactDOM.render(<Demo/>,document.getElementById('app'));
        </script>
      </body>
      

React之受控组件和非受控组件

非受控组件

  • 概念:现用现取(你不受我控制,我需要你的时候你再告诉我)

示例

<body>
  <div id="app"></div>
  <script src="../js/react.development.js"></script>
  <script src="../js/react-dom.development.js"></script>
  <script src="../js/babel.min.js"></script>
  <script type="text/babel">
    class Login extends React.Component {
      loginClick = (e) => {
          e && e.preventDefault();
          console.log(this.account.value,this.password.value);
        }
      render(){
        return (
          <form onSubmit={this.loginClick}>
            <span>账号</span>  
            <input ref={(e) => this.account = e} type='text' placeholder='请输入账号'/>
            <span >密码</span> 
            <input ref={(e) => this.password = e} type='password' placeholder='请输入密码'/>
            <button>登录</button>
          </form>
        )
      }
    }
    ReactDOM.render(<Login/>,document.getElementById("app"));
  </script>
</body>

受控组件

  • 概念:组件中输入类的DOM,随着用户的输入,将输入的值维护到state当中
    • 组件受到我控制,你随时都需要向我报告
 <body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">
      // 受控组件

      class Login extends React.Component {
        state = {
          account:'',//账号
          password:'',//密码
        }
        loginClick = (e) => {
          e && e.preventDefault();
          const {account,password} = this.state;
          console.log(account,password);
        };
        changAccount = (event) => {
          //event.target获取触发事件的DOM
          this.setState({
            account:event.target.value,
          })
        }
        changePwd = (event) => {
          //event.target获取触发事件的DOM
          this.setState({
            password:event.target.value,
          })
        }
        render() {
          return (
            <form onSubmit={this.loginClick}>
              <span>账号</span>
              <input type="text" onChange={this.changAccount} placeholder="请输入账号" />
              <span>密码</span>
              <input type="password" onChange = {this.changePwd}  placeholder="请输入密码" />
              <button >登录</button>
            </form>
          );
        }
      }
      ReactDOM.render(<Login />, document.getElementById("app"));
    </script>
  </body>
  • 效果

区别

  • 非受控组件就是现用现取,想获取什么值就去元素中获取
  • 而受控组件就是值统一存储在state的当中,需要的时候就去取
  • 说通俗点就是非受控组件组件不囤积’粮食’,没有食物了就去超时买而受控组件会**囤积’粮食’,**需要的时候就去仓库取

高阶函数和函数的柯里化

  • **高阶函数:**符合下面任意一个条件即为高阶函数

    1. 本身是一个函数,又返回了一个函数
    2. 函数当中的参数接受到了一个函数
  • 高阶函数常见的有Promise,setTimeout,arr.map()

  • **函数的柯里化:**通过函数调用继续返回函数的方式,实现多次接受参数最后统一处理的函数编码形式,比如下面

      <script type="text/javascript">
        //普通的函数
        function sum1(a,b,c){
          return a+b+c;
        }
        const result1 = sum1(1,2,3);
        console.log(result1);
    
        //函数的柯里化
        function sum2(a){
          return (b) => {
            return (c) => {
              return a+b+c;
            }
          }
        }
        const result2 = sum2(1)(2)(3);
        console.log(result2);
      </script>
    
  • 函数的柯里化在React的应用

    • 如下代码所示,通过一个函数就完成了二个参数的传入,达到了函数复用的效果
<body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">
      // 受控组件

      class Login extends React.Component {
        state = {
          account:'',//账号
          password:'',//密码
        }
        loginClick = (e) => {
          e && e.preventDefault();
          const {account,password} = this.state;
          console.log(account,password);
        };

        collectInfo = (type) => {
          return (event) => {
            this.setState({
              [type]:event.target.value,
            })
          }
        }

        render() {
          return (
            <form onSubmit={this.loginClick}>
              <span>账号</span>
              <input type="text" onChange={this.collectInfo('account')} placeholder="请输入账号" />
              <span>密码</span>
              <input type="password" onChange = {this.collectInfo('password')}  placeholder="请输入密码" />
              <button >登录</button>
            </form>
          );
        }
      }
      ReactDOM.render(<Login />, document.getElementById("app"));
    </script>
  </body>
  • 上面例子当中,不使用函数的柯里化在 collectInfo当中的实现
<body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">
      // 受控组件

      class Login extends React.Component {
        state = {
          account:'',//账号
          password:'',//密码
        }
        loginClick = (e) => {
          e && e.preventDefault();
          const {account,password} = this.state;
          console.log(account,password);
        };

        collectInfo = (event,type) => {
          //event为触发的DOM
          //type为类型
          this.setState({
            [type]:event.target.value
          })
        }

        render() {
          return (
            <form onSubmit={this.loginClick}>
              <span>账号</span>
              <input type="text" onChange={(event) => this.collectInfo(event,'account')} placeholder="请输入账号" />
              <span>密码</span>
              <input type="password" onChange = {(event) => this.collectInfo(event,'password')}  placeholder="请输入密码" />
              <button >登录</button>
            </form>
          );
        }
      }
      ReactDOM.render(<Login />, document.getElementById("app"));
    </script>
  </body>

关于类式组件当中的构造器

  • 类式组件中的构造器,完全可以省略掉

    <script type="text/babel">
        class Demo extends React.Component {
            //可以被省略
            //constructor(props){
            	//必须要调用
                //super();
            //}
            render(){
                return ''
            }
        }
    ReactDOM.render(<Demo name='李白'/>,document.getElementById('app'));
    </script>
    
  • 若在类式组件当中写了构造器,那么就必须要调用super

    <script type="text/babel">
        class Demo extends React.Component {
            constructor(props){
            	//必须要调用
                super();
            }
            render(){
                return ''
            }
        }
    ReactDOM.render(<Demo name='李白'/>,document.getElementById('app'));
    </script>
    
  • 若调用super时,不传props,那么在构造器当中,通过this.props不可以访问props的,反之就可以访问

    <script type="text/babel">
        class Demo extends React.Component {
            constructor(props){
                super();
                console.log(this.props); //输出undefined
            }
            render(){
                return ''
            }
        }
    ReactDOM.render(<Demo name='李白'/>,document.getElementById('app'));
    </script>
    

React生命周期

  • 生命周期是啥子就是一个组件从创建到销毁过程当中React会在特定阶段执行特定函数
  • 在类式组件当中,render方法会被调用1+n次,1次创建的时候被执行,n次则是数据被更新,DOM被更新的次数

生命周期16.x

  • 生命周期

    • constructor:构造器

    • componentWillMount:组件将要被挂载

    • render:组件初次渲染和后期更新(调1+n次,n为更新的次数)

    • componentDidMount:组件挂载完成后执行

      • 一般可以在这个钩子当中做一些初始的事情,如果说开启定时器,发送网络请求,订阅消息
    • componentWillUnmount:组件将要被卸载的时候执行(调1次)

      • 一般在这个钩子中做一些收尾的事情,比如关闭定时器,取消订阅消息
    • componentWillReceiveProps:props发生变化时候的回调

      • 初次渲染的时候不会执行此钩子
    • shouldComponentUpdate:组件是否可以更新,当返回值为true的时候允许更新,false禁止更新

    • componentWillUpdate:组件将要更新(调n次,n为更新的次数)

    • componentDidUpdate:组件更新完成(调n次,n为更新的次数)

  • 生命周期常用函数

    • fourceUpdate():不经过shouldComponentUpdate,直接更新组件
  • 卸载组件

    • ReactDOM.unmountComponentAtNode(要卸载的节点DOM)
    • 比如ReactDOM.unmountComponentAtNode(document.getElementById('app'));

示例1-透明度改变

  • 组件挂载完成后执行定时器,之后通过按钮执行卸载,卸载的时候执行组件卸载前生命周期函数
 <body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">
      class Demo extends React.Component {
        state = {
          opacity:1,
        }
        //生命周期-挂载完成
        componentDidMount(){
          this.timer = setInterval(() => {
            console.log('@');//组件卸载完成后不会再被输出
            let {opacity} = this.state;
            opacity = opacity-0.2 < 0 ? 1 : opacity-0.2 
            //设置透明度,每一次设置为上一次的0.2,当小于0的时候就重置
            this.setState({
              opacity,
            });
          }, 200);
        }
        //生命周期-组件将要被卸载
        componentWillUnmount(){
          //清除定时器
          clearInterval(this.timer);
        }
        dead = () => {
          //卸载组件
          ReactDOM.unmountComponentAtNode(document.getElementById('app'))
        }
        render(){
          //获取透明度
          const {opacity} = this.state;
          //造成render被多次调用,开启多个定时器
          // setInterval(() => {
          //   this.setState({
          //     opacity:opacity-0.2
          //   })
          // }, 200);
          return (
            <div>
              <p style={{opacity}}>叫我将军大人</p>
              <button onClick={this.dead}>就不叫你</button>
            </div>
          )
        }
      }
      ReactDOM.render(<Demo/>,document.getElementById('app'));
    </script>
  </body>

示例2-更新与强制更新

  • 每次单击按钮数字就会加一,同时React自动执行生命周期,通过另外一个按钮控制是否允许DOM更新
<body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">
      class Demo extends React.Component { 
        state = {
          number:1,
          allUpdate:true,
        }
        constructor(props){
          super(props);
          console.log('-- constructor --');
        }

        //单击数字+1
        addNumber = () => {
          const { number } = this.state;
          this.setState({
            number: number+1,
          })
        }
        
        //卸载组件
        xiezai = () => {
          ReactDOM.unmountComponentAtNode(document.getElementById('app'))
        }

        //控制是否允许DOM更新
        setShouldUpdate = () => {
          const {allUpdate} = this.state;
          this.setState({
            allUpdate:!allUpdate
          })
        }

        //强制DOM更新
        force = () => {
          this.forceUpdate();
        }

        /* 组件将要被更新 */
        componentWillUpdate(){
          console.log('-- componentWillUpdate --');
        }
        
        /* 组件是否允许被更新 */
        shouldComponentUpdate(){
          //返回为false,不允许
          //返回为true,允许 默认返回true
          return this.state.allUpdate;
        }

        /* 组件更新完毕 */
        componentDidUpdate(){
          console.log('-- componentDidUpdate --');
        }
        /* 组件将要被卸载 */
        componentWillUnmount(){
          console.log('-- componentWillUnmount --')
        }
        /* 组件初次渲染和后期更新 */
        render(){
          console.log('-- render --');
          return (
            <div>
              <span>{this.state.number}</span>
              <button onClick = {this.addNumber}>数字+1</button>
              <button onClick = {this.xiezai}>卸载组件</button>
              <button onClick = {this.setShouldUpdate}>更新是否允许更新</button>
              <button onClick = {this.force}>强制更新</button>
            </div>
          )
        }
      }
      ReactDOM.render(<Demo/>,document.getElementById('app'))
    </script>
  </body>

示例3-props的接收监听

  • 父组件传递props给子组件,子组件接收,后期父组件更新了props值,子组件就会调用componentWillReceiveProps
<body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">
      class A extends React.Component {
        state = {
          carName:'宝马',
        }

        updateValue = () => {
          this.setState({
            carName:'动感超人'
          })
        }

         /* props发生变化时候的回调 初次渲染的时候不会执行此钩子** */
         componentWillReceiveProps(){
          //不会触发
          console.log('-- componentWillReceiveProps --');
        }

        render(){
          const {carName} = this.state;
          return (
            <div>
              <button onClick={this.updateValue}>更新值</button>
              <B carName = {carName}/>
            </div>
          )
        }
      }

      class B extends React.Component {

        /* props发生变化时候的回调 初次渲染的时候不会执行此钩子** */
        componentWillReceiveProps(){
          //触发
          console.log('-- componentWillReceiveProps --');
        }
        render(){
          return (
            <div>
              <span>来自父组件当中的属性 - {this.props.carName}</span>
            </div>
          )
        }
      }

      ReactDOM.render(<A/>,document.getElementById('app'))
    </script>
  </body>

生命周期17.x

  • 17.x开始,React就不推荐使用下列三种生命周期钩子了,如需使用,需要在前添加前缀UNSAFE_

    • componentWillMount = > UNSAFE_componentWillMount
    • componentWillReceiveProps => UNSAFE_componentWillReceiveProps
    • componentWillUpdate => UNSAFE_componentWillUpdate
  • 与此同时也添加了几个新的生命周期钩子

    • getDerivedStateFromProps:如果该组件所有的state都取决于父组件传递过来的(也就是props),可以使用这个,这个函数必须要返回一个对象或者是null
      • 使用static getDerivedStateFromProps(props,state)
    • getDerivedStateFromError:一旦后台组件报错,就会触发,这个函数必须要返回一个对象或者是null
      • 使用static getDerivedStateFromError(error)
    • getSnapshotBeforUpdate:这个函数必须要配合componentDidUpdate使用且getSnapshotBeforeUpdate必须要有返回值,返回值为componentDidUpdate当中第三个形参
      • 使用getSnapshotBeforeUpdate(preProps,preState)
      • componentDidUpdate(prevProps, prevState, snapshot)
    • componentDidCatch:统计页面的错误,可以发送请求到后台去
      • 使用componentDidCatch(error,info)
  • 新生命周期钩子图示

示例1-使用不安全的生命周期钩子

 <body>
    <div id="app"></div>
    <script src="../js/17.0.1/react.development.js"></script>
    <script src="../js/17.0.1/react-dom.development.js"></script>
    <script src="../js/17.0.1/babel.min.js"></script>
    <script type="text/babel">
      class A extends React.Component {
        state = {
          carName:'宝马',
        }

        updateValue = () => {
          this.setState({
            carName:'动感超人'
          })
        }

         /* props发生变化时候的回调 初次渲染的时候不会执行此钩子** */
         UNSAFE_componentWillReceiveProps(){
          //不会触发
          console.log('-- componentWillReceiveProps --');
        }

       UNSAFE_componentWillMount(){
          console.log('-- UNSAFE_componentWillMount  --');
        }
        UNSAFE_componentWillUpdate(){
          console.log('-- UNSAFE_componentWillUpdate --');
        }
        render(){
          const {carName} = this.state;
          return (
            <div>
              <button onClick={this.updateValue}>更新值</button>
              <B carName = {carName}/>
            </div>
          )
        }
      }

      class B extends React.Component {

        /* props发生变化时候的回调 初次渲染的时候不会执行此钩子** */
        UNSAFE_componentWillReceiveProps(){
          //触发
          console.log('-- componentWillReceiveProps --');
        }
        render(){
          return (
            <div>
              <span>来自父组件当中的属性 - {this.props.carName}</span>
            </div>
          )
        }
      }

      ReactDOM.render(<A/>,document.getElementById('app'))
    </script>
  </body>

如果不添加UNSAFE_前缀,后有如下图警告

示例2-使用新钩子,体会聊天窗口有新消息出现时候的做法

  • 注意点如下

  • getSnapshotBeforeUpdate(preProps, preState):返回更新视图前的props,和state

  • componentDidUpdate(preProps,preState,fromsnapshot):第三个参数名称随意,接收的是来自getSnapshotBeforeUpdate返回的值

  • getSnapshotBeforeUpdatecomponentDidUpdate必须要连起来用,并且getSnapshotBeforeUpdate必须要返回一个值

<body>
    <div id="app"></div>
    <script src="../js/17.0.1/react.development.js"></script>
    <script src="../js/17.0.1/react-dom.development.js"></script>
    <script src="../js/17.0.1/babel.min.js"></script>
    <script type="text/babel">
      class Demo extends React.Component {
        state = {
          newsList: [],
        };

        getSnapshotBeforeUpdate(preProps, preState) {
          // console.log("snapshot", preProps, preState); //相同
          //更新前返回更新前的滚动条位置
          return this.wrapperNode.scrollHeight;
        }

        componentDidUpdate(preProps, preState, snapshot) {
          // console.log("componentDidUpdate", preProps, preState); //相同
          //设置滚动条位置
          //snapshot为更新前记录的值
          //而此时的this.wrapperNode.scrollTop 已经为更新后的距离了
          // this.wrapperNode.scollHeight - snapshot 就是新增加的内容所占据的高度
          this.wrapperNode.scrollTop += this.wrapperNode.scrollHeight - snapshot;
        }

        componentDidMount() {
          setInterval(() => {
            //添加定时器
            const { newsList } = this.state;
            const newsTemp = `新闻${newsList.length + 1}`;
            this.setState({
              newsList: [newsTemp,...newsList]
            });
          }, 1000);
        }

        render() {
          return (
            <div className="wrapper" ref={c => this.wrapperNode = c}>
              {
                this.state.newsList.map((item,index) => {
                  return <div key={index} className='item'>{item}</div>
                })
              }
            </div>
          );
        }
      }

      ReactDOM.render(<Demo />, document.getElementById("app"));
    </script>
  </body>
  <style>
    .wrapper {
      width: 200px;
      height: 150px;
      overflow: auto;
      background-color: hotpink;
    }
    .item {
      line-height: 30px;
    }
  </style>

没有使用getSnapshotBeforeUpdate的时候,效果如下,可以看到,每次有新消息出来,位置就会被改变,不会停留在一处,之前的为什么会被改变,那是因为浏览器会自动生成计算scrollTop

没有使用getSnapshotBeforeUpdate的时候

现在使用了getSnapshotBeforeUpdate,每一次更新前返回更新前的滚动条位置信息,通过componentDidUpdate手动设置scrollTop,从而每一次都在原处(为什么要减去的,因为每一次都增加了一部分,我想留在原地,当然要减去了)

那么原理是什么呢? 首先知道,scrollTop是设置(获取)这个元素的内容顶部(卷起来的)到它的视口可见内容(的顶部)的距离,所以如果我们不去改变滚动条,那么scrollTop的值是永远不会改变的(不相信你可以输出看看),scrollTop值不变,内容逐渐增多,就会导致内部容器滚动高度增长,所以就会看到位置逐渐后移的效果(再说通俗点就是修路,你站在原地,别人一直修路,随着路增长,你距离也会离他们越来越远)

<body>
    <div id="app"></div>
    <script src="../js/17.0.1/react.development.js"></script>
    <script src="../js/17.0.1/react-dom.development.js"></script>
    <script src="../js/17.0.1/babel.min.js"></script>
    <script type="text/babel">
      class Demo extends React.Component {
        state = {
          newsList: [],
        };

        getSnapshotBeforeUpdate(preProps, preState) {
          // console.log("snapshot", preProps, preState); //相同
          //更新前返回更新前的滚动条位置
          return this.wrapperNode.scrollHeight;
        }

        componentDidUpdate(preProps, preState, snapshot) {
          // console.log("componentDidUpdate", preProps, preState); //相同
          //设置滚动条位置
          //snapshot为更新前记录的值
          //而此时的this.wrapperNode.scrollTop 已经为更新后的距离了
          // this.wrapperNode.scollHeight - snapshot 就是新增加的内容所占据的高度
          this.wrapperNode.scrollTop += this.wrapperNode.scrollHeight - snapshot;
        }

        componentDidMount() {
          setInterval(() => {
            //添加定时器
            const { newsList } = this.state;
            const newsTemp = `新闻${newsList.length + 1}`;
            this.setState({
              newsList: [newsTemp,...newsList]
            });
          }, 1000);
        }

        render() {
          return (
            <div className="wrapper" ref={c => this.wrapperNode = c}>
              {
                this.state.newsList.map((item,index) => {
                  return <div key={index} className='item'>{item}</div>
                })
              }
            </div>
          );
        }
      }

      ReactDOM.render(<Demo />, document.getElementById("app"));
    </script>
  </body>

使用getSnapshotBeforeUpdate的时候

React的Differ算法

React/vue中的key有什么作用(key的内部原理是什么?)

  • 作用

    • key用于标识**「虚拟DOM」,当状态当中的数据发生改变的时候,React会根据新数据生成「新的虚拟DOM」,之后React会将「新虚拟DOM」「旧虚拟DOM」进行differ比较(比较的最小力度是节点(标签))**,规则如下
    • 旧虚拟DOM中找到了和新虚拟DOM相同的key
      • 若内容没有发生变化,就直接使用之前的真实DOM
      • 若内容发生了变化,则生成新的真实DOM,随后替换掉页面中与之对应的真实DOM
    • 旧虚拟DOM中未找到与虚拟DOM相同的key
      • 根据数据创建新的真实DOM,随后渲染到页面
  • 大概流程

为什么遍历列表的时候,key最好不要用index

  • 用index可能引发的问题
    • 若对数据进行:「逆序添加」,**「逆序删除」**等破坏顺序的操作
      • 会产生没有必须要的真实DOM更新 => 界面没有问题,但是效率低
    • 如果结构中包含输入类的DOM
      • 会产生错误的DOM更新 => 界面有问题
    • 如果不存在对数据的**「逆序添加」,「逆序删除」**等破坏顺序的操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
  • 开发如何选择key
    • 最好使用每条数据的唯一标识作为key,比如说id,手机号,身份证号,学号等唯一值
    • 如果确定只是简单的展示数据,用index也是可以的

React脚手架

  • 安装
npm install create-react-app -g
  • 创建一个名为react_01的react项目
create-react-app react_01
  • React脚手架创建的项目目录和文件分析

    • public:
      • favicon.ico => 网站图标
      • index.html => 主页面
      • logo192.png / logo512.png => logo图,不同分辨率的
      • manifest.json => 应用加壳的配置文件
      • robots.txt => 爬虫协议文件
    • src:
      • App.css => App组件的样式
      • App.js => App组件
      • App.test.js => 用于给App做测试
      • index.css => 样式
      • index.js => 入口文件
      • logo.svg => logo图,矢量图
      • reportWebVital.js => 页面性能分析文件(需要web-vitals库的支持)
      • setupTests.js => 组件单元测试文件(需要jest-dom库的支持)
  • 我们看下一个没见过的短命令在package.json当中

    • react-scripts eject:可以输出React当中的所有配置文件,过程不可逆
  • 一些小技巧

    • react脚手架当中**,可以省略.js ,.jsx后缀**,并且如果文件夹当中有index.js ,那么引入的时候就可以省略index.js
    • 比如import a from “/component/Person/index” => 可以省为 import a fro “/component/Person”
    • 引入css文件,只需要import "./XXXX.css" 即可
    • 如果组件需要传入全部的props值,可以使用<Item {...todos}/>,使用...进行展开

React小案例之todoList

  • 重要

    • 操作state数据的时候,只能通过setState去操作,不可以通过其他方式去更改

    • 比如不推荐如下

      let {todos} = this.state;
      todos.unshift(todoObj);
      this.setState({todos});
      
    • 推荐下面

      const {todos} = this.state;
      this.setState({
      todos:[todoObj,...todos],
      })
      
  • 知识点

    • 记住,状态驱动数据,数据驱动视图
    • 状态在哪里,操作状态的方法就在哪里
    • arr.reduce方法当中,如果没有传入initialValue,则第一次的preValue值就是数组中第一个元素的值
  • 技巧

    • 结构赋值重命名 const {a:newName} = obj;
  • 工具库

    • nanoid:一个小巧、安全、URL友好、唯一的 JavaScript 字符串ID生成器。

React脚手架配置代理

方式1-在package.json当中配置

  • 步骤
    • package.json当中添加"proxy": "http://localhost:5000/students"即可,其中http://localhost:5000代表的是服务器的地址
    • 然后我们发送ajax请求的时候
      • 之前会产生跨域问题axios.get('http://localhost:5000/students'),
      • 在package配置好后我们更改为axios.get('http://localhost:3000/students')即可
    • 总结
      • package是实际的服务器ip地址,axios是本地的地址
      • 缺点就是只能配置一台代理服务器~
  • 其他
    • 不知道为什么,我添加proxy,关闭服务重新启动就启动不了,提示Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.,删除后就可以启动了,所以这里就做一个学习吧,反正这方法也不太可能用到

方式1-src创建配置文件xxxx当中配置(推荐)

  • 1.在src目录下建立setupProxy.js(规范为cjs,也就是nodejs使用的规范)

  • 2.写如下配置

const proxy = require("http-proxy-middleware")

module.exports = function (app) {
    app.use(
        //api1是用于区分需要转发的请求,所有带有/api1前缀的请求都会转发给5000
        proxy('/api1',{
            target:'http://localhost:5000',//转换到的地址
            /*
                控制服务器接收到的请求头中的host字段的值
                如果配置了,在F12调试工具看到的依旧是本地3000端口,但是在服务器看到的却是5000端口
            */
            changeOrigin:true,
            pathRewrite:{
                '^/api1':'',//去除请求前缀,也就是去除 /api1
            }
        }),
        proxy('/api2',{
            target:'http://localhost:5001',
            changeOrigin:true,
            pathReWrite:{
                '^/api2':'',
            }
        })
    )
}
  • 关于changeOrigin

React小案例之github搜索

fetch和axios

  • fetch:为原生函数,不用安装也可以使用,不使用XMLHttpRequest,但是老版本可能不支持fetch

    • 想获取服务器返回的信息,就不需要像axios一样多访问一层data了,直接就返回的是原始数据

    • 想获取错误信息,依旧是要再访问一层message

    try {
    	let response = await fetch(`https://api.github.com/search/users?q=${value}`)
    	let result = await response.json();
    	PubSub.publish('updateList', {
    		userList:result?.items ?? [],
    		isLoading:false,
    	})
    }catch (e) {
    	PubSub.publish('updateList',{
    		isLoading: false,
    		errMsg:e.message,
    	})
    }
    
  • Promise之链式调用复习

    • 如果需要链式调用,需要可能返回一个promise,也可以返回一个非promise

      • 如果是返回promise,那么下一步的then的成功还是失败就取决于返回的promise
      • 如果是返回非promise,那么下一步的then状态一定是成功的
    • 链式调用示例

    const bbb = new Promise((resolve,reject)=>{
        let a = Math.random() * 100;
        if(a>50){
            resolve('第一次返回的resolve' + a);//标记为解决,并返回a - 第一次返回
        }else{
            reject('第一次返回的reject' + a);//标记为未解决,并返回a
        }
    });
    bbb.then(res=>{
        console.log(res);
        return new Promise((resolve, reject)=>{
            reject('第二次返回的reject')
        })
    })
        .then(res=>{
        console.log(res);//输出 第二次返回的reject
    })
        .catch(error=>{
        console.log(error);//统一处理错误,不在.then当中处理
    })
    
    //当然,你也可以写的更加简单点
    new Promise((resolve,reject)=>{
        let a = Math.random() * 100;
        if(a>50){
            resolve('第一次返回的resolve' + a);//标记为解决,并返回a - 第一次返回
        }else{
            reject('第一次返回的reject' + a);//标记为未解决,并返回a
        }
    }).then(res=>{
        console.log(res);
        return new Promise((resolve, reject)=>{
            reject('第二次返回的reject')
        })
    })
        .then(res=>{
        console.log(res);//输出 第二次返回的reject
    })
        .catch(error=>{
        console.log(error);//统一处理错误,不在.then当中处理
    })
    
    • 调用关系如下 相同颜色的代表为实际调用.then的promise

React之路由前置

预备,知道history

  • 知识点
    • 现实中的路由器-管理多个上网设备
    • 前端的路由器-管理多个页面
    • 现在主流的为SPA(也就是单页面,多组件)
    • 什么是哈希路由
      • 通俗点就是定位 #后面的都算的前端的东东,后端接收不到也不会将这些传递给后端
      • 比如 前端请求/abc#/a/b/c,那么向服务器请求的时候,后端收到的是/abc,而不会收到/a/b/c
    • replace操作
      • 原来从上到下是 /test1 ,/test2 后面replace(‘/test3’) 那么从上到下就依次变成了/ test1, /test3 原来的/test2被替换为了/test3
  • 示例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试history</title>
    <script src="https://cdn.bootcss.com/history/4.7.2/history.js"></script>
</head>
<body>
    <a href="http://www.baidu.com" onclick="return push('/test1')">添加一条历史记录</a>
    <button onclick="back()">回退</button>
    <button onclick="goNext()">前进</button>
    <button onclick="replace('/test3')">替换</button>
    <script>
        let history = History.createBrowserHistory();
        /*浏览器添加一条历史记录*/
        function push(path){
            history.push(path);
            return false;
        }
        
        function back(){
            history.goBack();
        }
        
        function goNext(){
            history.goForward();
        }
        function replace(path){
            history.replace(path);
        }
		//监听路径改变
        history.listen(location=>{
            console.log("请求路径发生了变化",location)
        })
    </script>
</body>
</html>

React路由@5版本

基本使用

  1. 安装react-router-dom库,这里以5.2.0版本为例子,6.x开始写法就和5.2.0不一样了

    1. 如果出现A <Route> is only ever to be used as the child of <Routes> element, never rendered directl就说明用5.2.0的版本的写法在6.x版本了,所以要么更改6.x的写法,要么更换react-router-dom为5.2.0版本
  2. index.js主入口文件最外层包裹一个<BrowserRouter></BrowserRouter>或者<HashRouter></HashRouter>,因为如果不这样子做,每次使用路由,都需要在使用的地点添加此标签,所以可以直接在主入口统一添加

    import React from "react";
    import ReactDOM from "react-dom";
    import App from "./App";
    import {BrowserRouter} from "react-router-dom";
    ReactDOM.render(
        (<BrowserRouter>
            <App/>
        </BrowserRouter>),document.getElementById('root'))
    
  3. 导航区的标签更换为Link标签或NavLink标签

    1. Link标签:点完要去一个地方,使用to来指明 <Link to="/about">前往About页面</Link>
    2. NavLink标签:点完要去一个地方,使用to来指明,和Link不同的是,多了activeClassName属性,可以指明选中路由的样式,如果不使用activeClassName指明活动类样式,那么会自动添加一个类名为active,使用和Link是一样的使用<NavLink activeClassName="activeStyle" to="/about">前往About页面</NavLink>
  4. 知道什么是一般组件(非路由组件)和路由组件

    1. 程序员没有直接写组件方式去使用组件,而是通过路由标签使用的形式,那么就是路由组件,否则的话就是非路由组件 还有就是路由组件不可以传递props属性.会收到固定的属性,而非路由组件(一般组件),可以接收到props
  5. 如果在路由组件输出this.props,会输出如下图内容

  1. 路由组件,接收到的三个固定的属性

    //操作历史记录的的方法
    history:
    	go: f go(n)
    	goBack: f goBack()
    	goForward: f goForward()
    	push: f push(path,state)
    	replace: f replace(path,state)
     
    location:
    	pathname: "/about"
    	//接收传递过来的search参数(也就是query参数)
    	search: ""
    	//接收传递过来的state参数
    	state: undefined
    
    
    match:
    	//接收传递过来的params参数
    	params:{}
    	path: "/about"
    	url: "/about"
    

基本使用示例代码

index.js

import React from "react"
import ReactDOM from "react-dom"
import App from "./App";
import {BrowserRouter} from "react-router-dom";

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

App.jsx

import React, { Component } from 'react'
import Home from './pages/Home'
import About from './pages/About'
import {Link,Route} from "react-router-dom"
export default class App extends Component {
    render() {
        return (
            <div>
                <div className="row">
                    <div className="col-xs-offset-2 col-xs-8">
                        <div className="page-header"><h2>React Router Demo</h2></div>
                    </div>
                </div>
                <div className="row">
                    <div className="col-xs-2 col-xs-offset-2">
                        <div className="list-group">
 							{/*导航栏*/}
                            {/*使用Link*/}
                            {/*<Link to='/about'>跳转到About</Link>*/}
                            {/*<Link to='/home'>跳转到HOME页面</Link>*/}

                            {/*使用NavLink*/}
                            <NavLink activeClassName="demo" to="/about">关于</NavLink>
                            <NavLink activeClassName="demo" to="/home">首页</NavLink>
                        </div>
                    </div>
                    <div className="col-xs-6">
                        <div className="panel">
                            <div className="panel-body">
                                {/*导航的结果*/}
                                <Route path='/about' component={About}></Route>
                                <Route path='/home' component={Home}></Route>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}

pages/About.jsx

import React, {Component} from 'react';

class About extends Component {
    render() {
        return (
            <div>
                我是About
            </div>
        );
    }
}

export default About;

pages/Home.jsx

import React, {Component} from 'react';

class Home extends Component {
    render() {
        return (
            <div>
                我是HOME
            </div>
        );
    }
}

export default Home;

二次封装NavLink

  1. 现在的组件基本上都是这样子<标签 属性A = "属性值" 属性B = "属性值">标签体</标签>,在react当中,标签体会放置在children属性当中,也就是可以通过this.props.children读取标签体内容
  2. 这样子就可以不用每次书写className了~,简化了我们操作
  3. MyNavLink示例如下代码
import React, {Component} from 'react';
import {NavLink,} from "react-router-dom";


class MyNavLink extends Component {
    render() {
        return (
            <div>
                {/* 接收传递过来的数据,并不论多少,都结构并赋值给NavLink组件 */}
                <NavLink className="list-group-item"  {...this.props}/>
            </div>
        );
    }
}

export default MyNavLink;

Switch的在路由的使用-遇见就返回

  • 正常情况下,react通过匹配路径的方式去寻找对应路径,并渲染,但是找到后不会停止寻找,而是会继续寻找

    //当我们切换到/about路径的时候
    
    //本意是想渲染 About组件,通过匹配路径的方式,然后匹配后react依旧会接着匹配
    	//匹配成功,显示
    <Route path="/about" component={About}/> 
        //匹配失败,不显示
    <Route path="/home" component={Home}/>
       //匹配成功,显示
    <Route path="/about" component={Other}/>
    
  • 这种找到后还接着寻找,很会影响效率,所以我们可以在外面嵌套一层Switch,(在vue当中就不用)

    import {Switch} from "react-router-dom";
    //当我们切换到/about路径的时候
    	//匹配成功,显示,不会接着往下匹配
    <Route path="/about" component={About}/> 
    
    <Route path="/home" component={Home}/>
       
    <Route path="/about" component={Other}/>
    

解决路径丢失的问题

  • 当我们路径从/about => 改为 /gugu/about的时候
<NavLink className="list-group-item" to="/gugu/about">About</NavLink>
<NavLink className="list-group-item" to="/gugu/home">HOME</NavLink>

<Route path="/gugu/about" component={About}/>
<Route path="/gugu/home" component={Home}/>
  • 切换路由后刷新页面,就会发现页面css丢失了

  • 可以看到,路由切换到/gugu/about后刷新页面,发现样式丢失了,原因如下
    • 在引入样式的时候,使用了 href="./css/bootstrap.css",也就是去寻找当前目录下的bootstrap,所以我们切换到/gugu/about,这个是一个多级路径,所以react会认为gugu300端口下的一个路径,所以就造成浏览器去访问http://localhost:3000/gugu/css/bootstrap.css了,正常的应该是寻找http://localhost:3000/css/bootstrap.css
  • 解决办法
    1. 引入样式的时候,去掉 . ,原来的 href="./css/bootstrap.css" => 改为 href="/css/bootstrap.css",这样子浏览器就会永远去3000端口下寻找东西
    2. 引入样式的时候, 原来的 href="./css/bootstrap.css" => 改为 href="%PUBLIC_URL%/css/bootstrap.css",让react永远将这个资源定位到public目录下的css目录
    3. 使用HashRouter(也就是哈希模式)
      • 因为之前我们说过,哈希模式,是不会将#后面的视为浏览器请求内容的,也就是说,即使你路径切换到了http://localhost:3000/#/gugu/home,#后面的/gugu/home浏览器都不会去使用的,查找资源的时候,只会使用#左侧指明的路径
        • 所以请求css的时候的请求地址始终为http://localhost:3000/css/xxxx
      • 考考你,当浏览器http://localhost:3000/abc/efg#/gugu/home请求一个css,那么url的请求地址会是什么?
        • 答案是http://localhost:3000/abc/efg/css

BrowserRouter和HashRouter的区别

  • 底层原理不一样
    • BrowserRouter使用的是H5的history API,不兼容IE9及以下版
    • HashRouter使用的是URL的哈希值
  • path表现形式不一样
    • BrowserRouter路径中没有**#**,例如:localhost:3000/demo/test
    • HashRouter的路径中包含**#,例如:localhost:3000/#/demo/test,并且#**后面的内容服务端是不会接收到的
  • 刷新后对路由state参数的影响
    • BrowserRouter没有任何影响(但是清空缓存后会有),因为state保存在history对象中
    • HashRouter老师我看是可以使用的,但是我使用就报Warning: Hash history cannot replace state; it is ignored,并且state输出也是undefined,所以估计HashRouter不支持传递state了
  • 备注
    • HashRouter可以解决一些路径错误相关的问题,因为并且**#**后面的内容服务端是不会接收到的,所以不管怎么样请求资源,都是会以localhost:3000开头的,不会出现localhost:3000/about这种开头

路由的匹配之模糊路由和精准(严格)匹配

  • 知道react的路由匹配是怎么匹配的 - 模糊匹配
    • path要的东西,to里面一个都不能少,其他你随意.并且path当中的顺序不能乱
      • 比如to = "/a/home/c" path = "/home/a/c" 那么react会将to拆分为 a 和 home 和 cpath拆分为 home 和 a 和 c ,path当中和to去匹配,发现第一个home 和 a 不对应,就不匹配下去了,就匹配失败了
      • 比如 to = "/home/c" path = "/home" 那么react会将to拆分为 home和cpath拆分为 home path当中和to去匹配,path当中的都与to都的匹配,所以就匹配成功了
  • 精准(严格)匹配开启
    • <Route exact={true}></Route> 或者 <Route exact ></Route> 即为开启严格匹配
  • 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

Redirect标签 - 重定向

  • 路由的兜底
  • 当路由都匹配不到的时候,就会使用Redirect当中的to值,放的顺序我试了,放在最前面也没有事
<Switch>
    <Route path="/about" component={About}/>
	<Route path="/home" component={Home}/>
    //当上面的都匹配不到的时候,就会匹配Redirect组件当中的to值
	<Redirect to="/home"/>
</Switch>

嵌套路由

  • 什么是嵌套呢,就是一个展示区里面又出现了另外一个展示区
  • 必须要知道的知识点-路由匹配
    • React的路由匹配的从最开始注册的路由开始匹配的,一直匹配到最后注册的路由(也就是从一级路由开始,二级路由继续后,三级路由继续,…一直继续下去)
    • 所以当我们路径切换到/home/news的时候,React会从一级路由开始(如果有)
      • 一级路由有 /home 和 /about 那么一级路由将会匹配 /home (因为是模糊匹配),所以会展示/home下的所有组件
      • 当我们来到/home的时候,就会开始二级路由匹配(如果有)
      • 二级路由有 /home/news 和 /home/message ,那么二级路由就会匹配/home/news,所以会展示/home/news下的组件
      • …如果存在三级,四级等,就会按照这个规律匹配下去
  • 所以为什么要慎重开始严格(精准)匹配,因为如果开启了严格
    • 如果路径变为了/home/news,React会从一级路由开始,那么/home/news,不会和/home进行匹配,因为开启了严格(精准)匹配
  • 示例(伪代码)
<div>
	<h3>我是HOME</h3>
	<ul className="nav nav-tabs">
		<li>
       			//自定义的NavLink,没封装什么 
			<MyNavLink to="/home/news">News</MyNavLink>
		</li>
		<li>
			<MyNavLink to="/home/message">Message</MyNavLink>
		</li>
	</ul>
	<Switch>
		{/* 注册路由 */}
		<Route path="/home/news" component={News}></Route>
		<Route path="/home/message" component={Message}></Route>
		{/* 兜底 */}
		<Redirect to="/home/news"></Redirect>
	</Switch>
</div>

路由传参之params

  • params传参,在vue,axios,react之中,都是这种形式/a/b/c/?/?/?/....

  • react添加params

    • 路由导航(路由链接):
      • <Link to="/home/message/1">
    • 注册路由(路由当中需要声明):
      • <Route path = "/home/message/:id" component = "ABC"/>
      • 如果存在多个也是这种写法<Route path = "/home/message/:id/:name" component = "ABC"/>
  • 接收参数: 子组件可以通过this.props.match.params接收传递的params参数,如果没有传params参数,则会返回空对象

  • 重新来看下路由接收到的三个固定的属性

//操作历史记录的的方法
history:
	go: f go(n)
	goBack: f goBack()
	goForward: f goForward()
	push: f push(path,state)
	replace: f replace(path,state)
 
location:
	pathname: "/about"
	//接收传递过来的search参数(也就是query参数)
	search: ""
	//接收传递过来的state参数
	state: undefined


match:
	//接收传递过来的params参数
	params:{}
	path: "/about"
	url: "/about"
  • 示例(伪代码)

//Message组件
render() {
        const { messageList } = this.state;
        return (
            <div>
                <div>
                    <ul>
                        {
                            messageList.map(item => {
                                return (
                                    <li key={item.id}><Link to={`/home/message/detail/${item.id}`}>{item.title}</Link></li>
                                )
                            })
                        }
                    </ul>
                </div>
                <div>
                    <Route path="/home/message/detail/:id" component={Detail}/>
                </div>
            </div>
        );
    }

//Detail组件
import React, {Component} from 'react';

class Detail extends Component {
    state = {
        list:[
            {id:1,title:"天气",info:'今天的天气很好的'},
            {id:2,title:"吃饭",info:'你吃饭了没有呀'},
            {id:3,title:"喜欢",info:'我喜欢小梦奇呀'},
        ]
    }
    render() {
        //接收传递过来的id
        const {id} = this.props?.match?.params;//字符串的id
        const {list} = this.state;
        //寻找item 找不到就返回一个空对象,否者下面如果undefined.id 就会报错
        const result = list.find(item => item.id == id) ?? {};
        return (
            <ul>
                <li>ID:{result.id}</li>
                <li>TITLE:{result.title}</li>
                <li>CONTENT:{result.info}</li>
            </ul>
        );
    }
}

export default Detail;

路由传参之search

  • search传参和vue,axios的query参数一样,也是通过?来区分,比如/a?name=abc&id=1这种形式

  • React添加search参数

    • **路由导航(路由链接)😗*当中<Link to="/demo/test?name=tom&age=18">信息</Link
    • 注册路由(search参数不需要声明):<Route path = "/demo/test" component = {Demo} />
  • 接收参数:

    • 子组件通过this.props.location.search进行获取,如果是没有传search参数,则会返回空字符串,否则就会返回字符串,比如?name=tom&age=18
  • 特别注意

    • 获取到的search是字符串(?name=tom&age=18) 所以需要通过querystring进行解析

    • querystring解析

      import qs from "querystring";
      const demo = '?name=tom&age=18';
      const result = qs.parse(demo.slice(1));
      //输出对象 注意,value均为字符串!
      // { name:'tom',age:'18' }
      
    • querystring编码

      import qs from "querystring";
      const demo = { name:'tom',age:'18' }
      const result = qs.stringify(demo)
      //输出字符串
      // "name=tom&age=18"
      
  • 伪代码示例

 //search传参
<li key={item.id}><Link to={`/home/message/detail?id=${item.id}`}>{item.title}</Link></li>

//search传参
<Route path = "/home/message" component={Detail}/>

/* search传参 */
const searchData = this.props.location.search;
const searchDataObj = qs.parse(searchData.slice(1));
const id = searchDataObj.id;
  • 重新来看下路由接收到的三个固定的属性
//操作历史记录的的方法
history:
	go: f go(n)
	goBack: f goBack()
	goForward: f goForward()
	push: f push(path,state)
	replace: f replace(path,state)
 
location:
	pathname: "/about"
	//接收传递过来的search参数(也就是query参数)
	search: ""
	//接收传递过来的state参数
	state: undefined


match:
	//接收传递过来的params参数
	params:{}
	path: "/about"
	url: "/about"

路由传参之state

  • React添加state参数

    • **路由导航(路由链接)😗*当中<Link to={{pathname:'/home/message',state:{id:666}}}>信息</Link
    • 注册路由(search参数不需要声明):<Route path = "/home/message" component = {Demo} />
  • 接收参数:

    • 子组件通过this.props.location.state进行获取,如果是没有传state参数,则会返回undefined,否则就会返回对象,比如{ id:666} 注意,这个666是number类型,而不是string类型,因为你在路由导航中传入的是number
  • 特别注意

    • state传参只适用于BrowserRouter 使用在HashRouter会出现react_devtools_backend.js:4026 Warning: Hash history cannot PUSH the same path; a new entry will not be added to the history stack,并且没有效果在哈希模式下
    • 刷新可以保留住参数,但是清空缓存会没有
  • 示例(伪代码)

//state传参
<li key={item.id}><Link to={{pathname:'/home/message',state:{id:item.id}}}>{item.title}</Link></li>
                                        
{/* state传参 */}
<Route path = "/home/message" component={Detail}/>
    
/* state传参 */
const searchData = this.props.location.state ?? {};
  • 重新来看下路由接收到的三个固定的属性
//操作历史记录的的方法
history:
	go: f go(n)
	goBack: f goBack()
	goForward: f goForward()
	push: f push(path,state)
	replace: f replace(path,state)
 
location:
	pathname: "/about"
	//接收传递过来的search参数(也就是query参数)
	search: ""
	//接收传递过来的state参数
	state: undefined


match:
	//接收传递过来的params参数
	params:{}
	path: "/about"
	url: "/about"

路由的push 和 replace

  • 默认情况下是push
  • 如果需要开启replace,只需要在路由声明当中添加replace即可
  • <Link replace to="/demo">跳转到Demo</Link><Link replace={true} to="/demo">跳转到Demo</Link>
  • 当然,你也可以直接就写一个单词<Link replace to="/demo">跳转到Demo</Link>

路由编程式导航

  • 编程式导航

    • 通过借助this.props.history对象上面的方法实现跳转,前进,后退,比如
      • this.props.history.push(path,state)
      • this.props.history.replace(path,state)
      • this.props.history.goBack()
      • this.props.history.goForward()
      • this.props.history.go(n)
    • push方式(默认)
      • 调用this.props.history.push(path,state),第一个path为路径,第二个state为state传参
    • replace
      • 调用this.props.history.replace(path,state),第一个path为路径,第二个state为state传参
  • 声明式导航

    • 在react当中就是通过Link 或者是 NavLink就是声明式导航
    • 在vue当中就是<router-link></router-link>
  • 示例(伪代码)

//编程式导航传递 - push
//<button key={item.id} onClick={() => this.pushShow(item.id)}>编程式-pushShow{item.title}</button>

//编程式导航传递 - params
<button key={item.id} onClick={() => this.replaceShow(item.id)}>编程式-replaceShow{item.title}</button>


/* 编程式导航 - replace */
replaceShow = (id) => {
	//params传参
	this.props.history.replace(`/home/message/detail/${id}`);

	//search传参
	//this.props.history.replace('/home/message/detail?id='+id);

	//state传参
	//this.props.history.replace('/home/message/detail',{id});
}

/* 编程式导航 - push */
pushShow = (id) => {
	//params传参
	//this.props.history.push(`/home/message/detail/${id}`);

	//search传参
	//this.props.history.push('/home/message/detail?id='+id);

	//state传参
	//this.props.history.push('/home/message/detail',{id});
}
  • 重新来看下路由接收到的三个固定的属性
//操作历史记录的的方法
history:
	go: f go(n)
	goBack: f goBack()
	goForward: f goForward()
	push: f push(path,state)
	replace: f replace(path,state)
 
location:
	pathname: "/about"
	//接收传递过来的search参数(也就是query参数)
	search: ""
	//接收传递过来的state参数
	state: undefined


match:
	//接收传递过来的params参数
	params:{}
	path: "/about"
	url: "/about"

路由的withRouter

  • 作用

    • withRouter可以让一般组件(非路由组件),可以让一般组件使用路由组件的相关API
    • withRouter调用后返回是一个新组件
  • 使用

    • 返回的时候用这个包裹一下
  • 示例(伪代码-在一般组件当中使用)

import {withRouter} from "react-router-dom";
import React,{Component} from "react";
import {withRouter} from "react-router-dom";
class Header extends Component {
    render() {
        return (
            <div className="row">
                <div className="col-xs-offset-2 col-xs-8">
                    <div className="page-header">
                        <h2>React Router Demo</h2>
                        {/* 如果是一般组件,那么this.props.history会是undefined */}
                        {/* 使用需要通过 withRouter来让一般组件可以使用路由组件的API */}
                        <button onClick={() => this.props.history.goBack()}>返回</button>
                        <button onClick={() => this.props.history.goForward()}>前进</button>
                    </div>
                </div>
            </div>
        )
    }
}
export default withRouter(Header)

React路由@6版本

概述

  1. React Router 以三个不同的包发布到 npm 上,它们分别为:

    1. react-router: 路由的核心库,提供了很多的:组件、钩子。
    2. react-router-dom: 包含react-router所有内容,并添加一些专门用于 DOM 的组件,例如 <BrowserRouter>
    3. react-router-native: 包括react-router所有内容,并添加一些专门用于ReactNative的API,例如:<NativeRouter>等。
  2. 与React Router 5.x 版本相比,改变了什么?

    1. 内置组件的变化:移除<Switch/> ,新增 <Routes/>等。

    2. 语法的变化:component={About} 变为 element={<About/>}等。

    3. 新增多个hook:useParamsuseNavigateuseMatch等。

    4. 官方明确推荐函数式组件了!!!

一级路由

  • 在react路由@6版本当中,我们必须要在所有的Route外面包一层Routes,作用和Switch类型,Routes中的Route只有一个会被匹配
  • Route不再使用component属性,而是element属性
  • <Route caseSensitive> 属性用于指定:匹配时是否区分大小写(默认为 false)。
  • 伪代码示例
import React from 'react'
import {NavLink,Route,Routes,Navigate} from "react-router-dom";
import About from "./views/About";
import Home from "./views/Home";
export default function App() {
    return (
        <div>
            <NavLink className="list-group-item" to="/home">HOME</NavLink>
            <NavLink className="list-group-item" to="/about">About</NavLink>
            <Routes>
                <Route path="/home" element={<Home/>}></Route>
                <Route path="/about" element={<About/>}></Route>
            </Routes>
        </div>
    )
}

Navigate组件-路由重定向

  • react路由@6当中,由于删除了Redirect,所以我们需要使用Navigate来达到重定向
  • Navigate只要被渲染出来,就会引起视图的切换
  • replace属性用于控制跳转模式(push 或 replace,默认是push)。
<Routes>
    <Route path="/home" element={<Home/>}></Route>
    <Route path="/about" element={<About/>}></Route>
    <Route path="/" element={<Navigate to="/home"/>}></Route>
</Routes>

import React,{useState} from 'react'
import {Navigate} from 'react-router-dom'

export default function Home() {
	const [sum,setSum] = useState(1)
	return (
		<div>
			<h3>我是Home的内容</h3>
			{/* 根据sum的值决定是否切换视图 */}
			{sum === 1 ? <h4>sum的值为{sum}</h4> : <Navigate to="/about" replace={true}/>}
			<button onClick={()=>setSum(2)}>点我将sum变为2</button>
		</div>
	)
}

NavLink组件

  • 在router@5版本当中,我们可以为NavLink添加activeClassName来指定激活状态下的样式

  • 但是在router@6版本当中,就没有这个了,取而代之的是我们需要通过函数来传递类名

  • 语法

    • className的方式
    //css内容
    .active_style{
        background-color: red;
    }
    
    
    //jsx内容
    import React from 'react';
    import {Link,NavLink} from "react-router-dom";
    import "./test.css";
    const Menu = () => {
        const activeStyle = ({isActive}) => {
            return isActive ? 'active_style' : null;
        }
        return (
            <div>
                <Link to={'/home'}>Home页面</Link>
                {/*<NavLink to='/home/page' className={activeStyle}>Home页面下的page</NavLink>*/}
                {/*或者*/}
                <NavLink to={'/home/page'} className={
                    ({isActive}) => {
                        return isActive ? 'active_style' : null;
                    }
                }>Home页面下的page</NavLink>
                <Link to={'/about'}>关于页面</Link>
            </div>
        );
    };
    
    export default Menu;
    
    //激活的时候HTML状态
    <a href="/home/page" aria-current="page" class="active_style">Home页面下的page</a>
    //未激活的时候HTML状态
    <a href="/home/page">Home页面下的page</a>
    
    • 或者styleName的方式
    import React from 'react';
    import {Link,NavLink} from "react-router-dom";
    const Menu = () => {
        const activeStyle = ({isActive}) => {
            return isActive ? {backgroundColor:'red'} : null;
        }
        return (
            <div>
                <Link to={'/home'}>Home页面</Link>
                {/*<NavLink to='/home/page' style={*/}
                {/*    ({isActive}) => {*/}
                {/*        return isActive ? {backgroundColor:'red'} : null*/}
                {/*    }*/}
                {/*}>Home页面下的page</NavLink>*/}
                <NavLink to='/home/page' style={activeStyle}>Home页面下的page</NavLink>
                <Link to={'/about'}>关于页面</Link>
            </div>
        );
    };
    
    export default Menu;
    
    //激活的时候HTML状态
    <a aria-current="page" class="active" href="/home/page" style="background-color: red;">Home页面下的page</a>
    
    //未激活的时候HTML状态
    <a class="" href="/home/page" style="">Home页面下的page</a>
    
    • isActive代表该路由标签是否是活动状态
  • 使用

 <div className="list-group">
                        <NavLink className={ ({isActive}) => isActive ? 'list-group-item demo' : 'list-group-item' } to="/home">HOME</NavLink>
                        <NavLink className={ ({isActive}) => isActive ? 'list-group-item demo' : 'list-group-item' }  to="/about">About</NavLink>
                        <NavLink className={ ({isActive}) => isActive ? 'list-group-item demo' : 'list-group-item' } to="/demo">Demo</NavLink>
                    </div>



//你也可以写成一个函数的形式
const getClassName = ({isActive}) => {
     return isActive ? 'list-group-item demo' : 'list-group-item'
}
<div className="list-group">
       <NavLink className={ getClassName } to="/home">HOME</NavLink>
       <NavLink className={ getClassName }  to="/about">About</NavLink>
       <NavLink className={ getClassName } to="/demo">Demo</NavLink>
</div>

useRoutes(类似于vue的路由表)

  • 在之前,我们是通过下面这样子书写注册路由的
<Routes>
	<Route path="/home" element={<Home/>}></Route>
	<Route path="/about" element={<About/>}></Route>
	<Route path="/demo" element={<Demo/>}></Route>
	<Route path="/" element={<Navigate to="/home"/>}></Route>
</Routes>
  • 有了useRoutes后,我们就可以通过此来构成这种结构

  • 语法

    • const element = useRoutes([ {path:匹配的路径,element:要渲染的组件,children:子路由结构相同} ])
  • 示例

import {NavLink,useRoutes} from "react-router-dom";
const element = useRoutes([
        {
            path:'/home',
            element:<Home/>
        },
        {
            path:'/about',
            element:<About/>,
        },
        {
            path:'/demo',
            element:<Demo/>
        }
])

<div className="list-group">
	<NavLink className="list-group-item" to="/home">HOME</NavLink>
	<NavLink className="list-group-item" to="/about">About</NavLink>
	<NavLink className="list-group-item" to="/demo">Demo</NavLink>
</div>

<div className="panel-body">
	{element}
	{/*<Routes>*/}
	{/*    <Route path="/home" element={<Home/>}></Route>*/}
	{/*    <Route path="/about" element={<About/>}></Route>*/}
	{/*    <Route path="/demo" element={<Demo/>}></Route>*/}
	{/*    <Route path="/" element={<Navigate to="/home"/>}></Route>*/}
	{/*</Routes>*/}
</div>

不过routes都是统一管理

  • 所以在src下建立router/index.js

  • index.js内容如下
import About from "../views/About";
import Home from "../views/Home";
import Demo from "../views/Demo"
export default [
    {
        path:'/home',
        element:<Home/>
    },
    {
        path:'/about',
        element:<About/>,
    },
    {
        path:'/demo',
        element:<Demo/>
    }
]
  • 我们在使用的时候就需要引入并生成路由表
import {useRoutes} from "react-router-dom";
const element = useRoutes(routes);

<div className="panel-body">
    //使用路由表 注册路由
	{element}
</div>

嵌套路由和Outlet

  • 嵌套路由,就是本身自己是一个路由组件,里面又嵌套了另外一个路由组件

  • 路由表和路由的书写技巧

    • 不可以写斜杆,写斜杆代表不管你从哪里开始,我都是从/开始的,
      所以我们需要在不破坏当前路由的基础上把路由加进去,所以不能有/
    • /带了斜杆就是根了(也就url的路径从我开始算起)
    • ./在不破坏当前路径下载后面添加内容,也可以不写./
    • 于是乎下面三个效果都是一样的,最终匹配的都是/home/message
    //路由表
    {
    	path:'/home',
    	element:<Home/>
    	children:[
        path:"./message",
        path:'/home/message',
        path:'message',
    	]
    }
    //路由链接的to的书写也和这个相同,
    //下面二个均是在对应url后面添加相应的内容
    //比如之前url为/home,那么点击`message组件显示`,
    //那么url就会变为/home/message
    <NavLink to="message">Message组件显示</NavLink>
    <NavLink to="news">News组件显示</NavLink>
    
  • 示例需求,在Home组件下建立二个子路由组件,我们先建立好如下的路由表

import About from "../views/About";
import Home from "../views/Home";
import Demo from "../views/Demo"

/*Home下方的二个子路由组件*/
import Message from "../views/Message"
import News from "../views/News";

export default [
    {
        path:'/home',
        element:<Home/>,
        //添加children属性,写法和上面一样
        children:[
            {
                //path:"./message",
                //或者
                //path:'/home/message',
                //或者,效果都是一样的
                path:'message',
                element:<Message/>
            },
            {
                path:'news',
                element: <News/>
            }
        ]
    },
    {
        path:'/about',
        element:<About/>,
    },
    {
        path:'/demo',
        element:<Demo/>
    }
]

  • 然后我们在Home组件当中书写路由链接,并使用Outlet进行占位显示(表示组件要显示的位置)
import {Outlet,NavLink} from "react-router-dom";
export default function Home() {
    return (
        <div>
            <h1>我是Home组件</h1>
            <div>
                <NavLink to="message">Message组件显示</NavLink>
                <NavLink to="news">News组件显示</NavLink>
            </div>
            <hr/>
            
            <div>需要展示的组件</div>
            <Outlet/>
        </div>
    )
}

效果

路由params传参

  • 传参

    1.在路由表当中使用占位
    	import Detail from "../views/Detail"
      {
          path:"detail/:id/:title/:content",
          /*params传参*/
          element:<Detail/>
      }
      
    2.路由传入参数
    const [list] = useState([
        {id:'001',title:'消息1',content:'动感超人1'},
        {id:'002',title:'消息2',content:'动感超人2'},
        {id:'003',title:'消息3',content:'动感超人3'},
        {id:'004',title:'消息4',content:'动感超人4'},
        {id:'005',title:'消息5',content:'动感超人5'},
    ])
    <ul>
          
    {list.map(item => (
    <li  key={item.id}>
      <Link to={`detail/${item.id}/${item.title}/${item.content}`}>
        {item.title}
      </Link>
     </li>
    ))}
    
    </ul>
    
    
  • 接收参数

    • 使用useParams获取params
      • 直接获取传入占位符所组成的对象
    import React from 'react';
    import {useParams} from "react-router-dom";
    const Detail = () => {
        const data = useParams();
        //{id: '003', title: '消息3', content: '动感超人3'}
        return (
            <div>
                <p>{data.id}</p>
                <p>{data.title}</p>
                <p>{data.content}</p>
            </div>
        )
    };
    
    export default Detail;
    
    
    • 使用useMatch获取params参数
      • 调用此函数需要传入完整的,包含占位的url
    import React from 'react';
    import {useParams,useMatch} from "react-router-dom";
    const Detail = () => {
        const data2 = useMatch('/home/message/detail/:id/:title/:content')
        const {params} = data2;
        return (
            <div>
                <p>{params.id}</p>
                <p>{params.title}</p>
                <p>{params.content}</p>
            </div>
        )
    };
    
    export default Detail;
    
    

    输出useMatch的返回值

路由search传参

  • 传参

    1.在路由表当中书写路由,search参数不需要任何占位
    	import Detail from "../views/Detail"
      {
          path:"detail",
          element:<Detail/>
      }
      
    2.路由传入参数
    const [list] = useState([
        {id:'001',title:'消息1',content:'动感超人1'},
        {id:'002',title:'消息2',content:'动感超人2'},
        {id:'003',title:'消息3',content:'动感超人3'},
        {id:'004',title:'消息4',content:'动感超人4'},
        {id:'005',title:'消息5',content:'动感超人5'},
    ])
    <ul>
          
    {list.map(item => (
    <li  key={item.id}>
      <Link to={`detail?id=${item.id}&title=${item.title}&content=${item.content}`}>
        {item.title}
      </Link>
     </li>
    ))}
    
    </ul>
    
    
  • 接收参数

    • 使用useSearchParams,用法有点类似useState
      • const [xxx,setXXX] = useSearchParams( )
      • xxx通过调用get方法,并传入要获取的key值,就可以得到对于search参数的值
      • setXXX传入search参数
    import React from 'react';
    import {useSearchParams} from "react-router-dom";
    
    const Detail = () => {
        const [search,setSearch] = useSearchParams();
        const id = search.get('id');
        const title = search.get('title');
        const content = search.get('content');
        return (
            <div>
                <p>{id}</p>
                <p>{title}</p>
                <p>{content}</p>
                <button onClick={() => setSearch('id=888&title=设置的标题&content=设置的内容')}>点击我调用setSearch方法</button>
            </div>
        )
    };
    
    export default Detail;
    
    

    当然了,xxx不光只有search,还有其他方法,类型推断出来的

    search的其他方法

调用setSearch

  • 当然,你也可以使用useLocation来获取参数
import {useLocation} from "react-router-dom"
console.log(useLocation())

输出查看useLocation

路由state传参

  • 传参
1.在路由表当中书写路由,search参数不需要任何占位
	import Detail from "../views/Detail"
  {
      path:"detail",
      element:<Detail/>
  }
  
2.路由传入参数
const [list] = useState([
    {id:'001',title:'消息1',content:'动感超人1'},
    {id:'002',title:'消息2',content:'动感超人2'},
    {id:'003',title:'消息3',content:'动感超人3'},
    {id:'004',title:'消息4',content:'动感超人4'},
    {id:'005',title:'消息5',content:'动感超人5'},
])


<ul>
{list.map(item => (
  <li  key={item.id}>
	  <Link to='detail' state = {{
			  id:item.id,
				  title:item.title,
					  content:item.content,
		  }}>
		  {item.title}
	  </Link>
  </li>
))}
</ul>

  • 接收参数

    • 使用useLocation
    import React from 'react';
    import {useLocation} from "react-router-dom";
    const Detail = () => {
        const { state:{id,title,content} } = useLocation()
        return (
            <div>
                <p>{id}</p>
                <p>{title}</p>
                <p>{content}</p>
            </div>
        )
    };
    
    export default Detail;
    
    

    输出查看useLocation返回值

编程式路由导航

  • 使用useNavigate即可
  • 示例
import {useState} from "react";
import {Link,Outlet,useNavigate} from "react-router-dom";
export default function Message() {
    const navigate = useNavigate();
    const [list] = useState([
        {id:'001',title:'消息1',content:'动感超人1'},
        {id:'002',title:'消息2',content:'动感超人2'},
        {id:'003',title:'消息3',content:'动感超人3'},
        {id:'004',title:'消息4',content:'动感超人4'},
        {id:'005',title:'消息5',content:'动感超人5'},
    ])
    const jump = ({id,title,content}) => {
        navigate('detail',{
            replace:false,
            state:{
                id,
                title,
                content,
            }
        })
    }
    return (
        <div>
            <ul>
                {list.map(item => (
                    <li  key={item.id}>
                        <Link to='detail' state = {{
                            id:item.id,
                            title:item.title,
                            content:item.content,
                        }}>
                        {item.title}
                        </Link>
                        <button onClick={() => jump(item)}>点击我也可以跳转</button>
                    </li>
                ))}
            </ul>
            <Outlet/>
        </div>
    )
}

  • useNavigate支持的参数
    • 官方地址说明@地址
declare function useNavigate(): NavigateFunction;

interface NavigateFunction {
  (
    to: To,
    options?: {
      replace?: boolean;
      state?: any;
      relative?: RelativeRoutingType;
    }
  ): void;
  (delta: number): void;
}

使用useNavigate进行页面后退,前进

  • 说明-摘自@官网
Pass the delta you want to go in the history stack. For example, navigate(-1) is equivalent to hitting the back button.
  • 示例
import {useNavigate} from "react-router-dom";

export default function Header() {
    const navigate  = useNavigate();
    const back = () => {
        navigate(-1);
    }
    const forward = () => {
        navigate(1);
    }
    return (
        <div className="col-xs-offset-2 col-xs-8">
            <div className="page-header"><h2>React Router Demo</h2></div>
            <div>
                <button onClick={back}>后退</button>
                <button onClick={forward}>前进</button>
            </div>
        </div>
    )
}

useInRouterContext()

  • 作用:如果组件在 <Router> 的上下文中呈现,则 useInRouterContext 钩子返回 true,否则返回 false。
  • useInRouterContext 什么时候不为true,当组件脱离了路由器的管理
  • 有时候一些封装的组件可能需要判断是否处于路由环境

useNavigationType()

  • 作用:返回当前的导航类型(用户是如何来到当前页面的)。

  • 返回值:POPPUSHREPLACE

  • 备注:POP是指在浏览器中直接打开了这个路由组件(刷新页面)。

useOutlet()

  • 作用:用来呈现当前组件中渲染的嵌套路由。

  • 示例代码:

const result = useOutlet()
console.log(result)
// 如果嵌套路由没有挂载,则result为null
// 如果嵌套路由已经挂载,则展示嵌套的路由对象

useResolvedPath()

  • 作用:给定一个 URL值,解析其中的:path、search、hash值。

redux

redux的核心概念

action

  • 和reducer打交道,通过action来告诉reducer应该怎么去操作中心仓库的值

store

  • 中心仓库,用于存储数据和接收reducer的初始化数据和改变后的数据

reducer

  • 和中心仓库直接打交道的值,返回的状态会影响到store

redux核心API

  • getState():获取状态

  • dispatch({type:xxxx,data:xxxx})操作reducer当中的方法,找到对应的case

  • store更新后视图更新:

    • 由于react中状态变了视图不一定会发生变化,也就是状态变了一定要重新调用render后视图才会变化
    • 所以这里需要监听store变化后改变,需要在index.js主入口文件填下如下内容
    import App from "./App";
    import React from "react";
    import ReactDOM from "react-dom";
    ReactDOM.render(<App/>,document.getElementById('root'));
    
    //监听store的变化,进而重新渲染
    store.subscribe( () => {
    	ReactDOM.render(<App/>,document.getElementById('root'));
    } )
    

redux编写案例

案例略,案例不是精髓,里面的笔记才是精髓~

redux简版的笔记

1.安装redux
	npm install redux 

2.src下建立
	-redux
		-store.js
		-count_reducer.js
3.编辑store.js文件
	//引入redux中的createStore函数,创建一个store
	import {createStore} from "redux";
	
	//引入为对应store服务的reducer 用于:初始化状态、加工状态
    import countReducer from "./countReducer";
	//暴露store对象 传入为store服务的reducer
    export default createStore(countReducer);
    
4.创建对应store服务的reducer:countReducer.js
	4.1:reducer的本质是一个函数,接收preState,action 返回加工后的状态
	4.2:reducer有二个作用,一个是初始化状态,一个是加工状态
	4.3:reducer被第一次调用是store自动触发的
		传递的previousState是undefined
		传递的action是:{type:'@@REDUX/INIT_a.2.b.4'}
	export default function countReducer(preState = 0 ,action) {
    const {type,data} = action;
    switch (type){
        case 'increment':
            return preState + data;
        case 'reduce':
            return preState - data;
        default:
            return preState;
    }
}

	
5.在index.js(主入口文件检测store中状态的改变,一旦发生改变重新渲染<App/>)
	import React from "react";
    import ReactDOM from "react-dom";
    import App from "./App";
    import store from "./redux/store";
    ReactDOM.render(<App/>,document.getElementById('root'));

    //监听store的变化,重新渲染
    store.subscribe(()=>{
        ReactDOM.render(<App/>,document.getElementById('root'));
    })

6.在count.js当中操作store

import React, {Component} from 'react';
import store from "../../redux/CountStore";
class Count extends Component {
    /*增加*/
    add = () => {
        //todo 先增加1
        store.dispatch({type:'increment',data:100})
    }
    render() {
        const storeData = store.getState();
        console.log(storeData)
        return (
            <div>
                <h1>状态值{storeData}</h1>
                <select>
                    <option value={1}>1</option>
                    <option value={2}>2</option>
                    <option value={3}>3</option>
                </select>
                <button onClick={this.add}>+</button>
                <button>-</button>
                <button>如果是奇数就增加</button>
                <button>异步加</button>
            </div>
        );
    }
}

export default Count;

redux完整版的笔记

  • 新增:action对象由专门的文件进行管理,比如count_action.js,就是为count所创建的action,当然也可以别的文件夹

    • 这样子我们在调用store.dispatch(action)就不需要手动写action了,而是通过一个变量或者函数来输入action值
    //比如原来让数字+1,我们在Count.jsx是这样子做的
    
    store.dispatch({type:'increment',data:1});
    
    //我们对action对象进行专门文件管理后就可以这样子
    
    1.创建一个action.js文件
    	//当然,你也可以对字符串'increment'单独建立一个文件夹
    	//便于统一管理action的type类型
    export function createIncrement(number){
    	return {type:'increment',data:number};
    }
    
    2.调用的时候只需要引入action.js当中对应的函数
    
    import {createIncrement} from "action.js"
    
    store.dispatch(createIncrement(1));
    
    
  • 新增:action对象当中的type也建立专门的文件进行管理,比如constance.js,就可以放置action的type,避免编码疏忽

redux-不包括异步的示意图

redux异步版的笔记

  • 什么是异步的呢?有点像nodejs的一些操作,既有同步操作,也有异步操作,比如nodejs的readFile操作

  • 之前的时候,我们把等待的操作放在了React组件当中,现在我们讲这个等待过程放在action里面去等待,由action定时等待后再去执行reducer(也就是把延迟的动作转交给action)

//之前的做法
<button onClick={this.addAsync}>异步加</button>

/*异步加*/
addAsync = () => {
	setTimeout(() => {
		const {selectDOM} = this.state;
		const {value} = selectDOM.current;
		store.dispatch(createIncrement(value));
	},1000)
}
  • 什么时候需要异步action: 想要对状态进行操作,但是具体的数据靠异步任务返回(非必须)

  • 具体编码(由于redux不支持改写法,所以需要借助中间件来处理)

    • 1.安装redux-thunk,在store添加如下配置

      import thunk from "redux-thunk";
      import {createStore,applyMiddleware} from "redux";
      import countReducer from "./countReducer";//自己的reducer
      
      export default createStore(countReducer,applyMiddlerWare(thunk));//使用中间件
      
    • 创建action函数不再返回一般对象,而是一个函数,在此函数中书写异步任务

    • 异步任务有结果后,分发一个同步的action去真正操作数据

      export const demo = (data,time) => {
      	return (dispatch) => {
      		setTimeout(() => {
      			//有结果了,分发一个同步的action
      			dispatch({type:'demo',data:data});
      		},time)
      	}
      }
      
    • 异步action不是必须要写的,完全可以自己等待异步任务的结果后再去分发同步action

redux包括异步的示意图

react-redux基本概念

  • 为什么需要使用react-redux
    • 在平时中,我们需要react-redux结合redux来使用
    • 单单使用redux操作过程事情太多了,不容易理解,操作和管理(面对大一点项目的时候就非常容易出现这种情况)
  • 先来学习下模型图吧,我感觉学习这个模型图,就是在学习设计思想,多学学总是没有错的
  • react-redux模型要求如下
    • 所有的UI组件都应该包裹一个容器组件,他们是父子关系(也就是src/component都是UI组件,src/container都是容器组件)
    • **容器组件是真正和redux打交道的,**容器组件可以随意的使用redux的api
    • UI组件不能使用任何redux的api(都需要借助于父亲(容器组件))
    • 容器组件会传给UI组件如下东东
      • redux中保存的状态
      • 用于操作状态的方法
    • 注意:容器给UI传递的状态方法都是通过props传递

react-redux操作代码

  1. store不变,依旧是使用redux

  2. 安装react-redux

  3. 明确二个概念

    1. UI组件:不能使用任何redux的api,只负责页面的呈现,交互等
    2. 容器组件:负责和redux通信,将结果交给UI组件
  4. 如果创建一个容器组件–通过react-redux 的 connect函数

    1. connect函数调用:connect(mapStateToProps,mapDispatchToProps)(UI组件)
      1. mapStateToProps映射状态,返回值是一个对象
      2. mapDispatchToProps映射操作状态的方法,返回值是一个对象
  5. 备注1:容器组件中的store的靠props传进去的,而不是在容器组件中直接引入的

  6. 备注2:apDispatchToProps也可以是一个对象

简化mapDispatch

  • 有几个需要注意的点在简写上,老师视频没有说,导致饶了很大弯路

    • 就是想简写mapDispatchToProps操作的时候,一定要直接传递对象,而不是通过一个函数返回对象
  • 之前的

import {connect} from "react-redux";
import Count from "../../Component/Count";
import {incrementAction,reduceAction,incrementActionAsync} from "../../Redux/Count/CountAction";
const mapStateToProps = (state) => {
    return {
        count:state,
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        add:(data) => dispatch(incrementAction(data)),
        increment: (data) => dispatch(reduceAction(data)),
        incrementAsync: (data,time) => dispatch(incrementActionAsync(data,time)),
    }
}
export default connect(mapStateToProps,mapDispatchToProps)(Count);
  • 简化后的
import {connect} from "react-redux";
import Count from "../../Component/Count";
import {incrementAction,reduceAction,incrementActionAsync} from "../../Redux/Count/CountAction";
const mapStateToProps = (state) => {
    return {
        count:state,
    }
}

//const mapDispatchToProps = () => ({
//    //incrementAction,
//    //reduceAction,
//    //incrementActionAsync
//})
export default connect(mapStateToProps,({
    incrementAction,
    reduceAction,
    incrementActionAsync
}))(Count);

  • 千万要注意的!千万要注意的!千万要注意的!千万要注意的!千万要注意的!千万要注意的!千万要注意的!千万要注意的!,如果想像上面这种简化形式的写法(不需要dispatch的那种),就一定要直接传递对象,而不是通过一个函数返回对象
//错误的,会导致state状态得不到更新,也不会自动调用dispatch,所以不可以这样子写
const mapDispatchToProps = () => ({
    incrementAction,
    reduceAction,
    incrementActionAsync
})
export default connect(mapStateToProps,mapDispatchToProps)(Count);

//下面这种写法才是正确的,直接在connect当中写对象
export default connect(mapStateToProps,({
    incrementAction,
    reduceAction,
    incrementActionAsync
}))(Count);

  • 还有值得一提的是,如果你是异步增加的时候,明明配置了中间件thunk,依旧提示报错 Actions must be plain objects. Instead, the actual type was:,那么可能是你action写法的问题
import {INCREMENT,REDUCE} from "./CountConstance";

export const incrementAction = data => ({type:INCREMENT,data});

export const reduceAction = data => ({type:REDUCE,data});


//可能看到,在使用简化操作之前,你可能这样子写的
export const incrementActionAsync = (data,time) => {
    setTimeout((dispatch)=>{
       dispatch(incrementAction(data))
    },time)
}

//但是实际需要这样子写,否者会报错
export const incrementActionAsync = (data,time) => {
    return (dispatch) => {
        setTimeout(()=>{
            dispatch({type:INCREMENT,data});
        },time)
    }
}

Provider

  • 为了避免出现下面这种多次传入store的情况
import React, {Component} from 'react';
import Count from "./Container/Count";
import {CountStore} from "./Redux";
class App extends Component {
    render() {
        return (
            <div>
                <Count store={CountStore}/>
                <Count store={CountStore}/>
                <Count store={CountStore}/>
                <Count store={CountStore}/>
                <Count store={CountStore}/>
                <Count store={CountStore}/>
                <Count store={CountStore}/>
                <Count store={CountStore}/>
                <Count store={CountStore}/>
            </div>
        );
    }
}

export default App;

  • 我们就可以使用react-redux当中的Provider组件,只需要在外面传入一次,App当中所有的后代容器组件都能接收到store
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import {Provider} from "react-redux";
import {CountStore} from "./Redux";
ReactDOM.render(
    <Provider store={CountStore}>
     <App />
    </Provider>, document.getElementById("root"));

整合UI组件和容器组件

  • 说通俗点就是将UI组件和容器组件写在一个文件里面,因为容器组件最终是暴露一个通过connect加工处理后的UI组件,所以写在一起也可以的

  • 伪代码示例

import React, {Component} from 'react';
import {connect} from "react-redux";

const mapStateToProps = (state) => {
    return {}
}

const mapDispatchToProps = (dispatch) => {
    return {}
}

class Count extends Component {
    render() {
        return (
            <div>
                <button onClick={this.addIfOdd}>奇数就加</button>
                <button onClick={this.addAsync}>异步加</button>
            </div>
        );
    }
}

export default connect(mapStateToProps,mapDispatchToProps)(Count);

数据共享(combineReducers)

  • 之前的store我们只在一个Count组件使用,这个时候我们可能不会这么简单,我们需要让这个redux可以被所有的组件所使用,并且可以区分数据是谁的
  1. 目录分级
    • 建立action文件夹:用于放置每一个数据所需的action
    • 建立reducer文件夹,用于放置每一个数据所需的reducer

就像这个样子

  • 使用combineReducers合并,合并后是一个总的状态对象

    import {createStore,applyMiddleware,combineReducers} from "redux";
    import thunk from "redux-thunk"
    import CountReducer from "./reducer/count";
    import PeopleReducer from "./reducer/people";
    
    const allReducer = combineReducers({
        count:CountReducer,
        people:PeopleReducer,
    })
    
    export default createStore(allReducer,applyMiddleware(thunk));
    
    
    • 使用combineReducers和没有使用的对比

  • 我们取值的时候就可以像下面这样子
import Count from "./Count";
const mapStateToProps = (state) => {
    return {
        result:state.count,
        listLength:state.people.length,
    }
}

export default connect(mapStateToProps,{})(Count);
  • 如果出现Error: Objects are not valid as a React child (found: object with keys {count, people}). If you meant to render a collection of children, use an array instead.,因为react不能直接展示对象,所以你错误原因就是展示了一个对象数据

image-20221126214350419

  • 下面这个代码用unshift为什么有问题,因为unshift没有返回值

    • 刚开始执行default,返回空数组,所以初始化的时候没有什么问题

      当type有值的时候,就会执行case ADDPEOPLE,然后return preInfo.unshift(data)运行后的结果,也就是一个null,和我们想象的返回添加后的数据是不一样的,或者你自己写一个代码块去处理也可以~

import {ADDPEOPLE} from "../const";
const initData = [];//初始化时候的值
export default function (preInfo = initData,action){
    const{type,data} = action;
    switch (type){
        //case ADDPEOPLE: return preInfo.unshift(data);
        case ADDPEOPLE: return [data,...preInfo];
        default: return preInfo;
    }
}
  • 再来看看这个为什么使用unshift有问题
    • 原理上:因为redux发现返回的地址值和之前是一样的,就不更新了
    • 其他:因为这个不是纯函数(这里导致传入的preInfo参数被修改了)
import {ADDPEOPLE} from "../const";
const initData = [];//初始化时候的值
export default function (preInfo = initData,action){
    const{type,data} = action;
    switch (type){
        case ADDPEOPLE: 
         preInfo.unshift(data);
         return preInfo;
        default: return preInfo;
    }
}

纯函数和高阶函数

  • 纯函数

    • 只要是输入同样的实参,就必定会得到相同的数据输出(返回)结果
    //无论今天,明天还是将来某个时间调用 Math.cos(0) 都没关系,输出始终为1,这就是一个纯函数
    
    //我们来看一个新的例子,下面函数totalApples就是一个纯函数
    const numberOfApples = 5;
    const applesBought = 5;
    const add = (num1, num2) => num1 + num2;
    const totalApples = add(numberOfApples, applesBought) // 10
    const totalApples = add(numberOfApples, applesBought) // 10
    // ... 过了很久很久
    const totalApples = add(numberOfApples, applesBought) //10
    
    • 不得改写参数数据(也就是你传入的参数,在函数内部不可以去修改)
    • 不会产生副作用
      • 函数执行的过程中对外部产生了可观察的变化,我们就说函数产生了副作用。
      • 例如修改外部的变量、调用DOM API修改页面,发送Ajax请求、调用window.reload刷新浏览器甚至是console.log打印数据,都是副作用
      • 说通俗点就是:你这个函数不可以访问外部的作用域和不可以让调试工具知道你这个函数干了什么(当然,debugger肯定不算),因为你发送ajax浏览器会捕捉,window.reload会进行刷新,console.log会进行控制台输出
  • 高阶函数

    • 参数是函数或者返回值是函数
    • 常见的高阶函数,forEach/map/filter/reducer/find/bind/promise

redux开发者工具的使用

  1. 安装

    • 谷歌浏览器插件市场搜索Redux DevTools即可
  2. 使用

    • 项目安装依赖(这个是已被废弃的插件),推荐用下一个
    npm install --save-dev redux-devtools-extension
    或者简写
    npm install -D redux-devtools-extension
    
    • 项目安装依赖(推荐)
    yarn add @redux-devtools/extension -D 
    npm install @redux-devtools/extension -D 
    
    • 项目使用
    import { createStore, applyMiddleware } from 'redux';
    import { composeWithDevTools } from '@redux-devtools/extension';
    
    const store = createStore(
      reducer,
      composeWithDevTools(
        applyMiddleware(...middleware)
        // other store enhancers if any
      )
    );
    

打包运行

  • 打包
yarn build
或者
npm run build
  • 打包后在build文件(注意,打包后的文件需要以服务端的形式运行)

  • 所以为了简单以服务端的形式运行,我们可以安装下面的插件
yarn add serve -g
或者
npm add serve -g
  • 安装后运行即可
serve 需要运行的东东

React扩展

setState更新状态的2种写法

  • setState(stateChange, [callback]) 对象式的setState

    • stateChange为状态改变对象(该对象可以体现出状态的更改)
    • callback是可选的回调函数,它在状态更新完毕,界面也更新后(render被调用)后才执行
      • 很有用其实,比如在状态更新后对DOM进行一些操作
  • 二. setState(updater, [callback]) 函数式的setState

    • updater为返回stateChange对象的函数
    • updater可以接收到state和props
    • callback是可选的回调函数,它在状态更新完毕,界面也更新后(render被调用)后才执行
      • 很有用其实,比如在状态更新后对DOM进行一些操作
  • setState是一个同步的方法,但是后续更新状态的动作是异步的

  • 示例

import React, {Component} from 'react';

class Demo extends Component {
    state = {
        age:18,
    }
    add = () => {
        //const {age} = this.state;
        //this.setState({
        //    age:age+1,
        //},() => {
        //    console.log('我更新完成了')
        //})
        this.setState((state,props) => {
            return {
                age:state.age + 1
            }
        })
    }
    render() {
        return (
            <div>
                <h1>当前年龄{this.state.age}</h1>
                <button onClick={this.add}>+1</button>
            </div>
        );
    }
}

export default Demo;
  • 总结
    • 对象式的setState是函数式的setState的简写方式(语法糖)
    • 使用原则(可以忽略,哪一个喜欢就用哪一个)
      1. 如果新状态不依赖于原状态 => 使用对象方式
      2. 如果新状态依赖原状态 => 使用函数方式
      3. 如果需要在setState() 执行后获取最新的状态数据,要在第二个callback函数中读取

lazy之路由的懒加载

  • 一句话,没有使用懒加载之前,你用到了几个路由组件,那么就会将这些路由组件全部加载过来,而现在我们做的就是使用到了的时候再去加载,加载的时候需要写一个加载等待的页面

  • 基本操作

    • 引入lazy,Suspense from “react”
    • 路由使用Suspense包括,并添加fallback属性
  • 使用示例如下

import React, {Component,lazy,Suspense} from 'react';
import {NavLink,Route} from "react-router-dom";
//import Home from "./Home";
//import About from "./About";
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
class Demo extends Component {
    render() {
        return (
            <div>
                <NavLink to='/home' activeClassName="demo">首页</NavLink>
                <NavLink to='/about' activeClassName="demo">关于</NavLink>
                <Suspense fallback={<h1>加载中...</h1>}>
                    <Route path='/home' component={Home}></Route>
                    <Route path='/about' component={About}></Route>
                </Suspense>
            </div>
        );
    }
}

export default Demo;

  • 报错Error: A React component suspended while rendering, but no fallback UI was specified. Add a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display.没有使用Suspense包裹或者没有书写fallback
//错误的
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
<div>
    <NavLink to='/home' activeClassName="demo">首页</NavLink>
    <NavLink to='/about' activeClassName="demo">关于</NavLink>
    <Route path='/home' component={Home}></Route>
    <Route path='/about' component={About}></Route>
</div>


//正确的
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
<div>
    <NavLink to='/home' activeClassName="demo">首页</NavLink>
    <NavLink to='/about' activeClassName="demo">关于</NavLink>
    <Suspense fallback={<h1>加载中...</h1>}>
        <Route path='/home' component={Home}></Route>
        <Route path='/about' component={About}></Route>
    </Suspense>
</div>

Hooks

  • Hook是React 16.8.0版本增加的新特性/新语法
  • 可以让我们在函数组件中使用state以及其他React特性

useState

  • 在之前,我们使用函数组件只能定义一些简单的组件(也就是没有state的组件),但是有了setStateHook,我们就可以使用state了
  • 基本语法
const [xxx,setXxx] = React.useState(initValue);

useState()说明
	参数:第一次初始化指定的值在内部做缓存
    返回值:包含二个元素的数组,第一个为内部当前状态值,第二个为更新状态值的函数

xxxx是初始值,只用来显示数据,直接修改不会触发组件的重新渲染
通过setXxx的修改值(不改变则不会重新渲染  )才会重新渲染(重新调用render)


//更新状态
setXxx()二种写法:
	setXxx(newValue):参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值
	setXxx(prevState => newValue);参数为函数,接收原来的状态值,返回新的状态值,内部用其覆盖原来的状态值
  • 示例
/*函数式组件*/
import React from "react";

export default function Demo(){
    /*使用setStateHooks*/
    const [result,setResult] = React.useState(0);
	//也可以创建多个
    const [resultB,setResultB] = React.useState(100);
    function add(){
        //第一种写法
        setResult(result + 1);
        //第二种写法
        //setResult(prevState => prevState + 1);
    }
    return (
        <div>
            <h1>计算结果是:{result}</h1>
            <button onClick={add}>结果+1</button>
        </div>
    )
}

  • 报错
    • React Hook "React.useState" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
//报错的写法,把这个React.useState写在了顶部

/*函数式组件*/
import React from "react";
/*使用setStateHooks*/
const [result,setResult] = React.useState(0);

function add(){
    //第一种写法
    setResult(result + 1);
    //第二种写法
    //setResult(prevState => prevState + 1);
}
export default function Demo(){

    return (
        <div>
            <h1>计算结果是:{result}</h1>
            <button onClick={add}>结果+1</button>
        </div>
    )
}
  • 疑问,不是说每一次渲染页面都会重新执行render吗?在函数式组件就是重新执行函数,那为什么状态值还是会更新,按照之前的理解不是应该都是0吗?
    • 因为react会在初次调用的时候保存这个状态值
  • 当state的值是一个对象时候,修改时是使用新的对象去替换原来的值
const [user,setUser] = useState({
	value:'猪八戒',
	age:18,
})

setUser({
	value:'孙悟空'
});//user被替换为了{value:孙悟空}


//所以不想其他值被替换,就需要扩展运算符
setUser({
	...user,
	value:'孙悟空',
})
  • 为什么使用新对象去替换呢?因为旧值和新值指向是同一个地址,react会默认为同一个,就不去更新了,所以set方法就需要整体替换才可以更新视图
const [user,setUser] = useState({
	value:'猪八戒',
	age:18,
})

user.value = '孙悟空';
setUser(user);//不会触发视图更新
  • setXxx改变的是下一次渲染时state的值
  • setState()会触发组件的重新渲染,它是异步的(不是立刻更新的),相当于有一个队列,剩下代码执行完成后才去执行队列当中的更新操作
const [user,setUser] = useState(0);
//调用一次函数则只执行一次视图更新
const add = () => {
    setUser(0);
    setUser(1);
    setUser(2);
}
  • 使用当setState需要使用旧值state,一定要注意有可能出现计算错误的情况,为了避免这种情况,可以通过为setState设置回调函数的形式来避免
const addHandler = () => {
	setTimeout(() => {
		setState(count + 1);//多次点击会出现问题
	},1000)
}

//改为回调形式的
const addHandler = () => {
	setTimeout(() => {
		setState((prevNumber) => {
			return prevNumber + 1;
		});
	},1000)
}
const addHandler = () => {
	setState((prev) => prev + 1)
}

useEffect

  • Effect Hooks:可以让我们在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
  • React中的副作用操作
    • 发ajax请求获取数据
    • 设置订阅 / 启动定时器
    • 手动更改真实DOM
  • 语法和说明
import React from "react";
React.useEffect(() => {
	//do something
	// ...
	
	//返回的函数将在组件卸载前执行
	return () => {
		//在这里做一些收尾工作,会在下一次effect调用前执行
		//比如清除定时器,取消订阅
	}

},[stateValue]) //如果指定的是 [], 那么回调函数只会在第一次render后执行
				//否者就会里面的值发生变化,就执行一次回调(因为render被重新执行了)
  • 我理解为:监听传入参数属性的变化,在组件加载后就立马执行一次回调,如果传入的参数为空数组,那么就不监听值的变化,只会在初次调用,如果不传入,那么会监听所有值的变化,发生变化就执行回调

  • 错误

    • Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.,你useEffect返回的是一个非函数了
    import React from "react";
    React.useEffect(() => {
    	//do something
    	// ...
    	
    	//返回的函数将在组件卸载前执行
    	return '我返回非函数';//报错,报上面的错误
    
    },[stateValue]) //如果指定的是 [], 那么回调函数只会在第一次render后执行
    				//否者就会里面的值发生变化,就执行一次回调(因为render被重新执行了)
    

useRef

  • 可以在函数组件中存储/查找组件内的标签或任意其他数据
  • 语法
import React from "react";
const myRef  = React.useRef();

<input ref={myRef} />
  • 作用:保存标签

  • 对象,功能与React.createRef()一样

  • 示例

import React from "react";
export default function Demo(){
    const myRef = React.useRef();
    function getValue(){
        const {value} = myRef.current;
        console.log(value)
    }
    return (
        <div>
            <input ref={myRef}/>
            <button onClick={getValue}>获取输入的值</button>
        </div>
    )
}
  • 其实,直接创建一个对象,对象当中有current也可以和useRef一样的效果
const h1Ref = {current:null}

<div ref={h1Ref}></div>
  • useRef()则可以确保每一次渲染获取的是同一个对象,这就是和自己直接创建的区别
  • 当需要一个对象不会因为组件的重新渲染而改变的时候,就可以使用useRef

Fragment

  • 使用
import {Fragment} from "react"

<Fragment></Fragment>

  • 作用
    • 可以不用必须有一个真实的DOM根标签了
  • 要参与遍历就需要使用Fragment,这样子就可以设置key值,不参与直接空标签
  • 或者使用空标签,空标签不能设置key值,
<>
	<input type='text'/>
</>
  • 不管是fragment还是空标签,都不可以设置css属性

Context和useContext

  • 一种组件通信方式,常用于[祖组件] [后代组件]间的通信

  • 一般用于插件

  • 遵循就近原则

    • 当我们通过context访问数据时候,他会读取离他最近的provider中的数据,比如下面,B组件读取的是沙和尚类似于作用域,如果没有provider,则读取context中创建的时候提供的默认值

  • 使用
0.引入React
	import React from "react";

1. 创建context容器对象
	const XxxContext = React.createContext(默认值);

2.渲染子组件,外面包裹XxxContext.Provider,通过value属性给后代组件传递数据
	<XxxContext.Provider value={数据}>
    	子组件
    </XxxContext.Provider>

3.后代组件读取数据
	//第一种:仅适用于类式组件
	class Demo extends Component {
        static contextType = XxxContext;
        this.context;//读取context中的value数据
		render(){
            //...
        }
    }
    
    //第二种:函数组件与类式组件都可以
    function Demo(){
        <XxxContext.Consumer>
            value => (
            	//value就是context中的value数据
            	//..要渲染的内容
        		)
        </XxxContext.Consumer>
    }

	

示例-类式组件接收

import React, {Component} from 'react';
import "./index.css";
// 1.创建context容器对象
const MyContext = React.createContext('未指定用户名称');

// 2.渲染子组件时,外面包裹context容器对象,并通过value属性给后代组件传递数据
    //value作为key是固定的,不可以更改


class A extends Component {
    state = {
        userName:'梦洁小站',
    }
    render() {
        return (
            <div className="A">
                <h1>我是A组件</h1>
                <div>我的用户名是:{this.state.userName}</div>
                <MyContext.Provider value={this.state.userName}>
                    <B/>
                </MyContext.Provider>
            </div>
        );
    }
}

/*直接父子一般不用这种方式*/
class B  extends Component {
    static contextType = MyContext
    render() {
        return (
            <div className="B">
                <h1>我是B组件</h1>
                <div>从A接收的用户名是:{this.context}</div>
                <C/>
            </div>
        );
    }
}


//3.接收使用
    //static contextType = context容器对象
class C  extends Component {
    static contextType = MyContext
    render() {
        //4.输出查看接收的context
        console.log(this.context)
        return (
            <div className="C">
                <h1>我是C组件</h1>
                <div>从A接收的用户名是:{this.context}</div>
            </div>
        );
    }
}

export default A;

示例-函数式组件接收

import React, {Component} from 'react';
import "./index.css";
// 1.创建context容器对象
const MyContext = React.createContext('未指定用户名称');

// 2.渲染子组件时,外面包裹context容器对象,并通过value属性给后代组件传递数据
    //value作为key是固定的,不可以更改


class A extends Component {
    state = {
        userName:'梦洁小站',
    }
    render() {
        return (
            <div className="A">
                <h1>我是A组件</h1>
                <div>我的用户名是:{this.state.userName}</div>
                <MyContext.Provider value={this.state.userName}>
                    <B/>
                </MyContext.Provider>
            </div>
        );
    }
}

/*直接父子一般不用这种方式*/
class B  extends Component {
    static contextType = MyContext
    render() {
        return (
            <div className="B">
                <h1>我是B组件</h1>
                <div>从A接收的用户名是:{this.context}</div>
                <C/>
            </div>
        );
    }
}

//3.函数式组件接收使用
function C(){
    return (
        <div className="C">
            <h1>我是C组件</h1>
            <MyContext.Consumer>
                {
                    value => (<div>从A接收的用户名是:{value}</div>)
                }

            </MyContext.Consumer>

        </div>
    );
}

export default A;

有多个数据需要传递也可以,传入一个对象就可以

const MyContext = React.createContext({});

class A extends Component {
    state = {
        userName:'梦洁小站',
        age:'18',
    }
    render() {
        return (
            <div className="A">
                <h1>我是A组件</h1>
                <div>我的用户名是:{this.state.userName}</div>
                <MyContext.Provider value={{...this.state}}>
                    <B/>
                </MyContext.Provider>
            </div>
        );
    }
}


//3.接收使用
    //static contextType = context容器对象
class C  extends Component {
    static contextType = MyContext
    render() {
        //4.输出查看接收的context
        console.log(this.context)
        return (
            <div className="C">
                <h1>我是C组件</h1>
                <div>从A接收的用户名是:{this.context.userName} - {this.context.age}</div>
            </div>
        );
    }
}

useContext(函数式组件)

除了上面集中使用context,还可以通过context来
//使用钩子函数(推荐)
import {useContext} from "react"
const ctx = useContext(创建Context的容器)
ctx即为context容器内容

组件优化

缺点

  • 目前我们使用继承Component来完成类式组件的书写,但是存在下面二个问题
    • 只要执行setState(),即使不改变状态数据,组件也会重新render()
    • 只要当前组件render()被执行了,就会自动重新调用子组件的render()
import React, {Component} from 'react';

class Demo extends Component {
    state = {
        name:'动感超人',
    }
    check = () => {
        this.setState({})
    }
    render() {
        console.log("父组件-render");
        return (
            <div>
                <div>{this.state.name}</div>
                <button onClick={this.check}>点击我</button>
                <Child/>
            </div>
        );
    }
}
class Child extends Component {
    render() {
        console.log('Child-render')
        return (
            <div>
                我是子组件
            </div>
        );
    }
}
export default Demo;

可以看到,不管有没有数据,只要调用了setState,数据就会被更新

效率高的做法

  • 只有当组件的state或props数据发生改变的时候才重新执行render()

原因

  • Component中的shouldComponentUpdate()总是返回true

解决

  • 方法1

    • 重写shouldComponentUpdate()方法,
    • 通过比较新旧state或props数据,如果有变化就返回true,没有就返回false
  • 方法2

    • 使用PureComponent

    • PureComponent重写了shouldComponentUpdate(),只有state或props数据发生变化了才返回true

    • 注意:

      • 只是进行stateprops数据的浅比较,如果知识数据对象内部数据变了,shouldComponentUpdate依旧会返回false,如下代码就是修改对象内部数据,就不会更新,也就是shouldComponentUpdate返回false了

        const changeCart = () => {
        	const obj = this.state;
        	obj.carName = '动感超人',
        	this.setState(obj);
        }
        
      • 所以需要产生新数据,不要直接修改state数据

    • 项目一般使用PureComponent来优化

  • PureComponent的例子

import React, {PureComponent} from 'react';

class Demo extends PureComponent {
    state = {
        name:'动感超人',
    }
    check = () => {
        this.setState({})
    }
    render() {
        console.log("父组件-render");
        return (
            <div>
                <div>{this.state.name}</div>
                <button onClick={this.check}>点击我</button>
                <Child/>
            </div>
        );
    }
}
class Child extends PureComponent {
    render() {
        console.log('Child-render')
        return (
            <div>
                我是子组件
            </div>
        );
    }
}
export default Demo;

render props(标签体)

  • 平时我们写的<div>中间内容</div>,这个"中间内容"我们叫他标签体内容
  • 标签体内容如果在react当中使用,比如<A>传入的值</A>,那么标签体内容再A组件当中就会出现在this.props.children当中

那么如果我们需要像vue插槽一样可以实现动态传入带内容的结构,在react当中怎么实现呢

  • 使用children的props属性,通过组件标签体传入结果
  • 使用render props:通过组件标签属性传入结构,一般用render函数属性

children props(通过组件标签体传入)

<A>
	<B>XXXX</B>
</A>
A可以正常渲染B组件,但是如果B需要A组件的数据无法做到
  • 示例
import React, {PureComponent} from 'react';

class Demo extends PureComponent {
    render() {
        return (
            <div>
                <A>
                    <B></B>
                </A>
            </div>
        );
    }
}

class A extends PureComponent {
    render() {
        console.log(this.props)
        return (
            <div>
                我是A
                {/* 如果需要渲染B组件 就需要像下面一行代码一样*/}
                {this.props.children}
            </div>
        );
    }
}


class B extends PureComponent {

    render() {
        return (
            <div>
                我是B
            </div>
        );
    }
}

export default Demo;

render props(通过组件标签属性传入)

  • 这样子我们就可以在A组件当中随意控制一个东西放置在里面了
<A render={ (data) => <C data={data}/> }></A>

A组件: 渲染:this.props.render(要传入的state当中的data数据)

C组件: 读取A组件传入的数据 this.props.data
  • 示例
import React, {PureComponent} from 'react';

class Demo extends PureComponent {
    render() {
        return (
            <div>
                <A render={(name) => <B name={name}/>}></A>
            </div>
        );
    }
}

class A extends PureComponent {
    state = {
        name:'梦洁小站',
    }
    render() {
        console.log(this.props)
        return (
            <div>
                我是A
                {/* 如果需要渲染B组件 就需要像下面一行代码一样*/}
                {/*接收的是一个函数,所以需要去执行,这个函数呢又有参数*/}
                {this.props.render(this.state.name)}
            </div>
        );
    }
}


class B extends PureComponent {

    render() {
        return (
            <div>
                我是B
                <h1>渲染来自A组件的name:{this.props.name}</h1>
            </div>
        );
    }
}

export default Demo;

错误边界

  • 错误边界(error boundary),用来捕获后代组件错误,渲染出备用页面
  • 特点
    • 只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件,定时器中产生的错误
  • 使用方式
    • getDerivedStateFromError配合componentDidCatch
    • 在生产环境下才可以看到效果,开发模式只会一闪而过
//生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error){
	console.log(error);
	//在render之前会触发,返回新的state
	return {
		hasError:true,
	}
}

//统计页面的错误,可以发送请求到后台去
componentDidCatch(error,info){
	//...
	console.log(error,info)
}
  • 示例
import React, {Component} from 'react';

class Index extends Component {
    state = {
        hasError:false,
    }
    static getDerivedStateFromError(error){
        return {
            hasError: true,
        }
    }
    render() {
        return (
            <div>
                我是A
                {this.state.hasError ? '网络出现错误' : <Child/>}
            </div>
        );
    }
}
class Child extends Component {
    state = {
        //list:[
        //    {name:'李白1',age:18},
        //    {name:'李白2',age:19},
        //    {name:'李白3',age:20},
        //]
        list:""
    }
    render() {
        return (
            <div>
                <h1>我是B</h1>
                {
                    this.state.list.map(item=><div key={item.name}>{item.name}</div>)
                }
            </div>
        );
    }
}
export default Index;

随记

  • 简称要全大写

  • reduce条件统计

  • 开启了严格模式,禁止函数的this指向window

    • 如果继承后想要有自己的属性,那么就需要在构造器当中去调用super,super必须要在this之前被调用

    • 如果继承后没有自己的属性,可以省略super,甚至构造器都可以省略不写

    • 如果继承后和父类有相同的名字函数,不是被覆盖了,而是在这一个函数之前,有同名的函数了,所以当依据原型链去寻找的时候,找到了一个,就不会接着找下去了,(就近原则),达到了重写的目的

    • 类中书写的属性

      • 在state

      • 静态属性

        • 只可以类名.属性名调用
        • static关键字修饰
      • 实例属性

        class Person {
            static num = 20;
            address = '深圳'
            constructor(n1,a1){
            this.name = n1;//实例属性
            this.age = a1;//实例属性
        }
        }
        
  • 如果有null,可能是暗示你书写一个对象

  • axios当中的then返回的参数是axios封装的一层对象,想要获取真正结果,需要多一层data才可以,也就是res.data才是获取真实结果

    • 同理,axios的catch也是,需要通过error.message才是获取错误信息
  • src目录为程序写的东东

  • 对象不能直接展示在react当中

  • kebab-case 烤肉串形式命名,全部都是小写的,通过’-'穿在一起

  • Reactredux createStore 在 typescript 中被弃用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/193768.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图扑软件 | 虚拟电厂负荷控制系统可视化

前言 随着国家“双碳”及“构建以新能源为主体的新型电力系统”等目标的提出&#xff0c;清洁化、数字化越来越成为电力系统面临的迫切需求&#xff0c;负控系统的发展对电力营销现代化建设具有重要的意义。 负控管理系统是一个着眼于全面加强电力信息管理的&#xff0c;集负…

Query 聚类

为了提高阅读体验&#xff0c;请移步到&#xff1a;Query 聚类背景搜索系统优化长尾 query。想了解一下长尾 query 长什么样&#xff1f;大体上都有几类&#xff1f;最好能归类&#xff0c;一类一类处理。Query 数据源&#xff1a;包含“什么”&#xff0c;“怎么”&#xff0c…

儿童台灯怎么选对眼睛好?2023开学必买的儿童台灯

l 通过国家卫健委发布的数据——2020年儿童青少年总体近视率高达52.7% l 爱尔眼科视光研究所的数据——6岁儿童中45%已失去远视储备&#xff0c;6-10岁近视度数增长最快 l 孩童近视程度的发展之外&#xff0c;让人猝不及防 l 在光照环境中&#xff0c;能给孩子们提供最好的阅…

力扣468验证IP地址C++判断合法IP字符串

目录前言题目描述解题思路主功能函数分类大框架判断IPv4是否合法判断IPv6是否合法其余小边界条件(调试后得)完整代码前言 这是一道常见的笔试面试题,我找实习已经碰到两次了&#xff0c;和矩阵的乘法出现频率一样高&#xff0c;你校招要是全程没遇到可以过来打我;(这道题大厂面…

SAP IFRS 17 面向服务架构详细解析(包含分类账规则)

经过漫长的旅程,国际会计准则委员会 (IASB) 于 2017 年 5 月发布了 IFRS 17“保险合同”(IFRS 17)。IFRS 17 取代了 2004 年发布的 IFRS 4。总体目标是提供一个更加透明和全球签发保险合同的实体之间保险合同的统一会计模型。在 IFRS 17 标准发布三年后,IASB 于 2020 年 6 月…

Linux下tomcat服务器的html文件部署

本章介绍使用Xshall发布命令,远程操作Linux系统,在Linux上搭建Java程序部署环境 本文章只是简单介绍html文件的部署,不涉及连接数据库 1.安装JDK 使用yum安装openjdk与windows中的jdk虽然不同,但是功能相似,这里的yum相当与中央管理仓库,包含了各种各样的软件 列出jdk相关的…

【黄啊码】PHP结合vue导出excel乱码

在这之前我们先回顾以前用php导出excel&#xff0c;我直接写成方法在这里&#xff1a; public static function phpExcelList($field, $list, $title文件,$file_time){$spreadsheet new Spreadsheet();$sheet $spreadsheet->getActiveSheet();foreach ($list as $key >…

【靶机】vulnhub靶机clover:1

靶机下载地址&#xff1a; Clover: 1 ~ VulnHub 靶机ip&#xff1a;192.168.174.145 Kali ip&#xff1a;192.168.174.128 靶机ip发现&#xff1a;sudo arp-scan -l 靶机开放端口扫描 分析&#xff1a; 发现开放了21端口ftp服务&#xff0c;且允许匿名登录 22端口ssh服务 8…

【交换机转发工作原理【泛洪、转发、丢弃】】

新的一年 新的征程 新的课程开班 等你来学&#xff01; ​一、交换机的三张表 在讲交换机转发工作原理前&#xff0c;先介绍交换机的三张表&#xff1a; 1.ARP缓存表&#xff1a;ARP(Address Resolution Protocol)地址解析协议&#xff0c;主要作用将目的IP地址解析(映射…

Linux系统中GDB调试详细操作方法

第一&#xff1a;启动 在shell下敲gdb命令即可启动gdb&#xff0c;启动后会显示下述信息&#xff0c;出现gdb提示符。 添加编译指令&#xff1a;gcc -g a.c -o test 打开指令&#xff1a;gdb test 或者 gdb 然后输入&#xff1a;file test ➜ example gdb …

数据结构:简单排序方法(插入排序和起泡排序)

1、插入排序 插入排序(insertion sort)的基本操作是将当前无序序列区 R[i…n]中的记录 R[i]“插入”到有序序列区 R[1…i-1]中,使有序序列区的长度增 1,如图所示。 例如,对下列一组记录的关键字: (49,38,65,76,27,13 ,91,52) &#xff08;3-4&#xff09; 进行插人排序的过…

【c语言】数据结构-带头双向循环链表

主页&#xff1a;114514的代码大冒险 qq:2188956112&#xff08;欢迎小伙伴呀hi✿(。◕ᴗ◕。)✿ &#xff09; Gitee&#xff1a;庄嘉豪 (zhuang-jiahaoxxx) - Gitee.com 目录 共八种链表&#xff1a; 前言 一、结构简图与分析 二、链表操作详解&#xff08;附代码实现&am…

js:原生ajax【纯js】

同步与异步区别同步&#xff1a;按顺序&#xff0c;依次执行&#xff0c;向服务器发送请求-->客户端做其他操作异步&#xff1a;分别执行&#xff0c;向服务器发送请求>同时执行其他操作原生ajax创建ajax对象var anew xmlhttprequest();设定数据的传输方式&#xff08;ge…

电容笔和触控笔的区别是什么?触控笔排行榜

电容笔和触控笔在导电材料、作用机理、用途等方面来看&#xff0c;都有着很大的不同。电容笔采用设计中等大小的笔头&#xff0c;而且采用更耐磨的材料。随着科技的进步&#xff0c;人们的生活质量也在不断提高&#xff0c;无论是在工作中&#xff0c;还是在学习中&#xff0c;…

电子仿真软件MultiSIM

Multisim是美国国家仪器(NI)有限公司推出的以Windows为基础的仿真工具&#xff0c;适用于板级的模拟/数字电路板的设计工作。它包含了电路原理图的图形输入、电路硬件描述语言输入方式&#xff0c;具有丰富的仿真分析能力。 目前MultiSIM最新版本为14.2&#xff0c;可通过NI的…

利用ArcGIS进行生态敏感性的分析

【分析说明】 生态敏感性是指生态环境遭外界的干扰和侵入时&#xff0c;生态系统受损害的可能性大小&#xff0c;它可衡量外界干扰对生态环境造成的危害程度&#xff0c;通常生态敏感性越高&#xff0c;生态环境越容易受外界因素的影响地形、植被、水体方面的生态因子及其对该…

this作用全解(全局this 、函数this、全局函数 this call apply bind……)

文章目录this 是什么全局上下文的 this函数上下文的 this全局上下文中的函数对象中的函数箭头函数构造函数显式改变函数上下文的 thiscallapplybindthis 是什么 老是记不住 this 指向谁&#xff0c;我觉得这是非常正常的现象&#xff0c;因为 this 指向的情况本身就比较多&…

linux secure boot(安全启动)下为内核模块签名

文章目录linux secure boot(安全启动)下为内核模块签名背景Secure Boot安全启动开启关闭方法内核驱动签名生成签名证书和私钥导入签名证书BIOS(UEFI)导入证书(重要)制作带签名的驱动参考linux secure boot(安全启动)下为内核模块签名 背景 随着计算机性能和存储空间的提升&am…

Mybatis对象关联数据库表【对多关联AND对一关联】

一对多分成两部分&#xff1a;对一、对多 1. 准备两张表oder、customer 2. 对多关联实现 对多关联&#xff1a;Customer类下创建一个List集合OrderBean类 public class Customer {private Integer id;private String name;//对多关联private List<OrderBean> orderBeanL…

结构型设计模式 Structural Patterns :适配器 Adapter(Python 实现)

文章大纲 python 代码实现实现1实现2实现3解决实际问题的例子参考文献与学习路径使用示例: 适配器模式在 Python 代码中很常见。 基于一些遗留代码的系统常常会使用该模式。 在这种情况下, 适配器让遗留代码与现代的类得以相互合作。 识别方法: 适配器可以通过以不同抽象或…