React全家桶

1.React介绍

React起源与发展

React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决

定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源

了。

为什么要学习React

传统的JavaScript或者之前学习的jquery 操纵DOM 非常的繁琐(编码麻烦),并且效率低下(因为只要我们操纵DOM 那么浏览器就会频繁的对页面进行绘制与排列 )

                document.getElementById("xxxx");
        document.getElementsByClassName("xxx");
        document.getElementsByTagName("xxxx");
                $(".xxx")
                $("#xxx")
        ...
        ...

传统的JavaScript操纵DOM 浏览器会进行大量的重绘重排

传统的JavaScript没有组件化编码方案,代码复用性非常低

React是什么?

官方: React是一个用于构建用户界面的javascript库

react是一个将数据渲染为HTML的开源javascript库

React的特性

1.采用组件化模式 声明式编码 提高开发效率以及组件化复用性

2.在ReactNative中可以使用React语法进行移动端开发

3.使用虚拟DOM+diff算法尽量减少与真实DOM的交互

vue和react的区别

区别:

1.数据。vue:双向数据绑定和单向数据流。双向数据绑定:DOM元素绑定的data值,当发生改变后,vue的响应式会监听到data的变化重新渲染。单向数据流:当父组件给子组件传递数据的时候,子组件只可以读取而不能修改数据。可以用watch监听数据的更改,再赋给父组件的变量。

react:单向数据流。DOM 元素依赖于state,但改变state不会改变渲染好的DOM,通过setstate()才能重新渲染。父组件传值到子组件,如果顶级的props变了,会重新渲染所有的子组件。

2.虚拟DOM。vue:计算出虚拟DOM 的差异,在渲染的过程中,跟踪每个组件的依赖关系,不会重新渲染整个组件树。

react:当应用的状态改变时,重新渲染全部子组件,可以通过shouldComponentUpdate生命周期进行优化。

3.模板和jsx。vue:具有单文件组件,可以把html、css、js写在一个vue文件里------------------MVVM框架

react:依赖于jsx,在JavaScript中创建DOM------------------视图层框架

虚拟Dom--VirtualDom与diff算法

传统的dom操纵会在数据改变的时候 重新把页面的所有内容都绘制一遍 (哪怕是10000条数据 之多了一条 那么他也会重新渲染10001次)

虚拟dom--快减少更新次数 减少更新区域 提高性能

虚拟dom相当于在js和真实dom中间加了一个缓存。基于React进行开发时所有的DOM构造都是通过虚拟DOM进行,每当数据变化时,React都首先重新构建整个DOM树(减少页面更新次数),然后React将当前整个DOM树和上一次的DOM树进行对比(DOM Diff算法-计算出虚拟DOM中真正变化的部分),得到DOM结构的区别,然后仅仅将需要变化的部分进行实际的浏览器DOM更新。

React与传统MVC的关系

轻量级的视图层库!

React不是一个完整的MVC框架,最多可以认为是MVC中的V(View),甚至React并不非常认可MVC开

发模式;React 构建页面 UI 的库。可以简单地理解为,React 将界面分成了各个独立的小块,每一个块

就是组件,这些组件之间可以组合、嵌套,就成了我们的页面。

2.HelloWord16.8版本

16.8是react的一个分水岭 其中有很多新增特性 在企业中使用中使用

跟多高版本的特性在后面会慢慢学到

1.获取依赖包

(1)react.js文件是创建React元素和组件的核心文件

(2)react-dom.js文件用来把React组件渲染为DOM,此文件依赖于react.js文件,需在其后被引入。

(3)Babel的主要用途是将ES6转成ES5 同时可以把JSX 语法转换新标准的JavaScript代码让现今浏览器兼容的代码

2.编写代码

<!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>Document</title>
    <!-- 1.引用react核心库文件 -->
    <script src="react.16.8.6.js"></script>
    <!-- 2.引用react支持dom操作的react-dom -->
    <script src="react-dom.16.8.6.js"></script>
    <!-- 3.引用babel -->
    <script src="babel.min.js"></script>
</head>
<body>
    <!-- 4.创建一个容器 用来容纳后续渲染内容 -->
    <div id="demoDiv"></div>
​
    <script type="text/babel">/* 5.此处不要错了 让babel解析其中的内容*/
        // 6.创建虚拟dom
        let VDom= <h1>你好么么哒!!!</h1>
        // 7.把创建的虚拟dom渲染到页面上
        ReactDOM.render(VDom,document.getElementById("demoDiv"))
​
    </script>
</body>
</html>

思考

虽然react 官方推荐我们使用jsx的方式来创建虚拟dom 但是有同学就会好奇 为什么官方要推荐使用jsx来创建虚拟dom

3.VirtualDom的两种创建方式

我们要完成如下图的页面dom

方式1 使用jsx来创建虚拟DOM

其实jsx的方式创建虚拟dom我们刚才在写helloword的时候已经使用过了 我们只需要稍加改造就行了

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
​
    <script type="text/babel">
        //使用()代表是个整体 直接添加id 与 内容
        let VDom=(
                <h1 id="xixi">
                    <span>你坏</span>
                </h1>
                ) 
        ReactDOM.render(VDom,document.getElementById("demoDiv"))
​
    </script>
</body>
</html>

方式2 使用原生js来创建虚拟DOM

原生js中使用 document.createElement()来新建dom节点

在我们引用React之后 可以使用React.createElement(标签名,标签属性,标签内容)来创建虚拟dom

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <!-- 由于使用原生 那么就不需要babel了 -->
    <!-- <script src="babel.min.js"></script> -->
</head>
<body>
    <div id="demoDiv"></div>
    <!-- 由于使用原生那么就不用babel -->
    <script type="text/javascript">
          
        // let VDom=React.createElement(标签名,{标签属性},标签内容)
        let VDom=React.createElement("h1",{id:"xixi"},React.createElement("span",{},"你好"))
        ReactDOM.render(VDom,document.getElementById("demoDiv"))
​
    </script>
</body>
</html>

总结

jsx出现的原因 就是因为 传统方式创建虚拟DOM太麻烦了 所以才需要JSX来创建虚拟DOM简化创建虚拟DOM的复杂度(也可以理解为jsx就是传统dom操作的语法糖--语法糖也叫糖衣语法就是用简单的语法完成之前复杂的事情

4.虚拟DOM与真实DOM区别

我们查看下虚拟dom与真实dom到底是什么?

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
​
    <script type="text/babel">
        
        let VDom=(
                <h1 id="xixi">
                    <span>你坏</span>
                </h1>
                ) 
        ReactDOM.render(VDom,document.getElementById("demoDiv"))
​
        console.log("虚拟dom",VDom);
        console.log("真实dom",document.getElementById("demoDiv"));
​
    </script>
</body>
</html>

大家会发现浏览器中显示 虚拟dom就是一个我们常见的普通对象

但是虚拟dom和真实dom里面是什么呢?

我们使用debugger看一下

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
​
    <script type="text/babel">
        
        let VDom=(
                <h1 id="xixi">
                    <span>你坏</span>
                </h1>
                ) 
        ReactDOM.render(VDom,document.getElementById("demoDiv"))
​
        let ZDom=document.getElementById("demoDiv")
        console.log("虚拟dom",VDom);
        console.log("真实dom",ZDom);
        // 设置断点
    
        debugger
​
    </script>
</body>
</html>

在浏览器中查看会发现

虚拟dom

真实dom

对比后会发现

虚拟dom会比较 而真实dom 比较 因为虚拟dom只需要在react内部在使用需要使用那么多属性

总结

1.虚拟dom就是一个我们常见的普通对象

2.虚拟dom会比较 而真实dom 比较 因为虚拟dom只需要在react内部在使用需要使用那么多属性

3.虚拟dom最终会被react转换成真实dom 展现在页面上

5.jsx语法规则

jax=javascript xml (xml是一种早起存储数据的格式 是一种要求语法非常严谨的数据格式--现在都使用json)

jsx对语法的要求非常的严格 严格到变态

一个根标签

多行标签需要有一个容器进行包裹

语法严谨

标签必须闭合 必须按照w3c规范来编写

Jsx变量 属性插值 与 注释

定义虚拟dom的时候不加双引号 同时jsx遇见{} 会当js表达式解析 遇见<>当html解析

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
​
    <script type="text/babel">
        let text="xixi";
        let demotext="你好"
​
        let VDom=(
                <h1 id={text}>
                    {/*插入变量*/}
                    <span>{demotext}</span>
                </h1>
                ) 
        ReactDOM.render(VDom,document.getElementById("demoDiv"))
​
    </script>
</body>
</html>

总结:

插入变量: 因为在jsx中遇见{}就会把里面的东西当成js表达式 解析 所以如果我们想插入变量 就把这个变量放到{我是变量}

插入属性: 因为在jsx中遇见{}就会把里面的东西当成js表达式 解析 所以我们如果想给属性插入变量 就把这个变量直接放到 属性={变量}

插入注释:因为在jsx中遇见{}就会把里面的东西当成js表达式 解析 所以我们注释 {/* 我是注释 */}

样式

行内样式

行内样式需要写入一个样式对象

注意语法 第一个{}是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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
​
    <script type="text/babel">
        let text="xixi";
        let demotext="你好"
​
        let VDom=(
                <h1 id={text}>
                    <span style={{color:"red",backgroundColor:"yellow"}}>{demotext}</span>
                </h1>
                ) 
        ReactDOM.render(VDom,document.getElementById("demoDiv"))
​
    </script>
</body>
</html>

类样式

jsx中类名不能使用class来设置(因为class是js的关键字)使用className来设置

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
    <style>
        .demo{
            color:red;
            background-color: yellow;
        }
    </style>
</head>
<body>
    <div id="demoDiv"></div>

    <script type="text/babel">
        let text="xixi";
        let demotext="你好"

        let VDom=(
                <h1 id={text}>
                    <span className="demo">{demotext}</span>
                </h1>
                ) 
        ReactDOM.render(VDom,document.getElementById("demoDiv"))

    </script>
</body>
</html>

数据遍历

在日常工作中我们进场需要把数据进行遍历展示到页面中 那么在jsx中怎么遍历呢?

特殊情况(工作不会遇见)

如果数据是一个数组 那么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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
          
        let arr=["EZ","VN","MF","NOC"]
        let arrb=[<li>EZ</li>,<li>VN</li>,<li>MF</li>,<li>NOC</li>]
        let VDom=(
            <div>
                <ul>
                {/*
                    大家会发现ract会把我们的数据进行遍历
                    但是这样也有问题就是 ul中不会自动生产标签
                */}
                    {arr} 
                </ul>   
                
                <ul>
                {/*
                    虽然这样可以遍历出来数据 但是工作中 没有那个后端会给我们返回arrb这										样的数据  所以这种方式行不通
                */}
                    {arrb}
                </ul>   
            </div>
        )
        ReactDOM.render(VDom,document.getElementById("demoDiv"))

    </script>
</body>
</html>

那么怎么办呢?

遍历数据

首先我们在遍历数据的时候可以使用 数组的map方法 因为map方法可以遍历数据并且返回出新的内容

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
          
        let arr=["EZ","VN","MF","NOC"]
        
        let VDom=(
            <div>
               <ul>
                    {
                        arr.map((v,i)=>{
                            return (
                                <li key={i}>{v}</li>
                            )
                        })
                    }
                </ul>
            </div>
        )
        ReactDOM.render(VDom,document.getElementById("demoDiv"))

    </script>
</body>
</html>

注意

在jsx中 一对大括号中 放的是js表达式

表达式:通过计算可以返回结果的公式 所以上面的map就是一个表达式 因为map可以返回一个新的结果

但是注意 jsx大括号中不能放置 if switch for 这些js语句 因为他们只是控制程序的执行顺序 不能返回新的结果 所以他们不是表达式

但是注意 jsx大括号中不能放置 if switch for 这些js语句 因为他们只是控制程序的执行顺序 不能返回新的结果 所以他们不是表达式

但是注意 jsx大括号中不能放置 if switch for 这些js语句 因为他们只是控制程序的执行顺序 不能返回新的结果 所以他们不是表达式

6 面向组件编程

组件与组件化 模块与模块化

模块与模块化

模块:用来封装可以重复使用的js代码块

模块化:整个项目都是使用模块的方式来完成的

组件与组件化

组件: 用来封装重复使用的ui代码块

组件化:整个项目都是使用组件的方式来完成的

组件的概念

组件就是把ui部分拆分成一个个独立的并且可以重复使用的部件 在吧这些部件拼装在一起 形成一个页面

组件的设计目的是提高代码复用率,降低测试难度和代码的复杂程度。

1 .提高代码复用率:组件将数据和逻辑进行封装。 2 .降低测试难度:组件高内聚低耦合(各个元素高集成度低关联性),很容易对单个组件进行测试。 3 .代码的复杂程度:直观的语法,可以极大提高可读性。

React Dev Tools

注意:React Dev Tools是谷歌浏览器的插件 所以别的浏览器不行

在开发原生js的时候,我们经常使用浏览器自带的开发者工具,它足以帮助我们查看和调试js中变量的各种信息。

对于react框架来说,因为它是采用动态渲染生成的代码结构,因此,我们需要一种可以分析react代码结构和变量状态的工具,而react dev tools 就是这样的工具

安装

安装方式1

如果你有科学上网工具 直接可以在谷歌浏览器商店中搜索下载

安装方式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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
      	// 首字母大写
        // 首字母大写
        // 首字母大写
        // 首字母大写
        function Fun(){
            return (
                <div>
                    <h1>我是一个函数组件</h1>    
                </div>
            )
        }
        ReactDOM.render(<Fun/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

调试

使用刚才安装的调试工具查看

类组件

类组件,顾名思义,也就是通过使用ES6类的编写形式去编写组件,该类必须集成React.Component 其中有一个render的渲染方法 里面有段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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
       class Fun extends React.Component{
        render(){
            return (
                <div>
                    <h1>我是一个类组件</h1>    
                </div>
            )
        }
       }
        ReactDOM.render(<Fun/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

函数组件与类组件的区别

函数组件的创建形式使代码的可读性更好,并且减少了大量冗余的代码,大大的增强了编写一个组件的便利

函数组件不会被实例化,整体渲染性能得到提升,无实例化过程也就不需要分配多余的内存,从而性能得到一定的提升。

函数组件由于没有实例化过程,所以无法访问组件this中的对象,若想访问this就不能使用这种形式来创建组件

函数组件无法访问生命周期的方法

思考

既然上面说了 函数组件没有this 而类组件中有this 那么类组件中的这个this里面有什么?

7 属性1-state 组件状态(数据/变量)

state状态机

大家发现上图中有state这个属性

state属性:

  1. state 是组件对象最重要的属性,值是对象(可以包含多个 key-value 的组合)

  2. 组件被称为“状态机”(状态机制),通过更新组件的 state 来更新对应的页面显示(重新渲染组件)

1.初始化state 读取

默认情况下 state中是null 我们需要在类组件中初始化 所以我们可以放到 comstructor中进行初始化操作

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
       class Fun extends React.Component{
        // 初始化
        constructor(props){
            super(props)
            // 1.设置state
            this.state={
                bool:true
            }
        }
        render(){
            return (
                <div>
                {/*2.使用state状态*/}
                    <h1>我是一个类组件--{this.state.bool?"你好":"你坏"}</h1>    
                </div>
            )
        }
       }
        ReactDOM.render(<Fun/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

2.修改state

更新状态需要调用 this.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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
       class Fun extends React.Component{
        constructor(props){
            super(props)
            this.state={
                bool:true
            }
        }
        fun=()=>{
            // 修改需要使用setState
            this.setState({
                bool:!this.state.bool
            })
        }
        render(){
            return (
                <div>
                    <h1 onClick={this.fun}>我是一个类组件--{this.state.bool?"你好":"你坏"}</h1>    
                </div>
            )
        }
       }
        ReactDOM.render(<Fun/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

setState调用后发生了什么

setState是异步的

(如果有大量数据修改的话不会因为修改数据而造成程序的卡顿)

import React, { Component } from 'react'

export default class demob extends Component {
    // 初始化state状态数据需要放到constructor中进行初始化
    // es6中继承的规则中得知 子类是可以不写constructor 他会在实例化的时候
    // 自动补充一个

    // 但是如果你写了 那么必须在其中写super()  因为super就是调用父类的构造方法
    // 此时子类才有this
    constructor() {
        super()   
        // 初始化state 
        this.state={
            text:"我是字符串",
            num:888,
            bool:true,
            arr:[1111,2222,333],
            obj:{
                name:"xixi"
            }
        }
    }

    fun=()=>{
        // 修改state的数据
        // this.setState({
        //     num:123,
        //     text:"xixi"
        // })
        // 下面的console.log打印的结果是修改之后的  还是修改之前的?
        // 是修改之前的结果  所以从而证明了setState是一个异步任务
        // console.log(this.state.num)

        // 但是我就是想setState修改完数据之后  打印新的结果怎么办?
        // 因为setState是异步   异步都会有回调函数
        this.setState({
            num:123,
            text:"xixi"
        },()=>{
            // setState第二个参数是一个回调函数  当数据修改完他才会调用
            console.log(this.state.num)

        })
     
    }


    render() {
        return (
            <>
                {/* 2.使用state数据 */}
                <h1>我是测试state使用的例子----{this.state.num}---{this.state.text}</h1>
                <button onClick={this.fun}>点我修改</button>

            </>
        )
    }
}

调用了setState之后会自动触发render渲染

render就是渲染方法 只有render方法执行了 那么页面才会根据数据的改变而随之发生改变

render就是渲染方法 只有render方法执行了 那么页面才会根据数据的改变而随之发生改变

render就是渲染方法 只有render方法执行了 那么页面才会根据数据的改变而随之发生改变

render就是渲染方法 只有render方法执行了 那么页面才会根据数据的改变而随之发生改变

state的简写写法

因为类中可以直接编写赋值语句所以我们的state也可以直接创建 简化了写法

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
       class Fun extends React.Component{
        // 创建state
            state={
                bool:true
            }
        fun=()=>{
            // 修改需要使用setState
            this.setState({
                bool:!this.state.bool
            })
        }
        render(){
            return (
                <div>
                    <h1 onClick={this.fun}>我是一个类组件--{this.state.bool?"你好":"你坏"}</h1>    
                </div>
            )
        }
       }
        ReactDOM.render(<Fun/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

hook-useState

useState

useState 是reactHOOK给我们提供的 最基本最常用的一个HOOK 主要作用就是用来管理当前本地的状态

useState() 返回值是一个数组(长度为2)数组的第一项标识的是当前的值 数组的第二项标识的时候修改这个值的函数

let [xiaoming , setXiaoming]=useState(初始值)

创建与读取

import {useState} from "react"
let Funcom=()=>{
    // 使用useState()c创建函数组件的状态
    let [xiaoming,setxiaoming]=useState("你好么么哒!!!")
    return (
        <div>
            {/* 读取 */}
            <h1>我是一个函数组件--{xiaoming}</h1>
        </div>
    )
}
export default Funcom

修改

import {useState} from "react"
let Funcom=()=>{
    // 使用useState()创建函数组件的状态
    let [xiaoming,setxiaoming]=useState("你好么么哒!!!")
    return (
        <div>
            {/* 读取 */}
            <h1>我是一个函数组件--{xiaoming}</h1>
            {/* 修改数据 */}
            <button onClick={()=>{setxiaoming(xiaoming="你好呵呵哒")}}>点我修改</button>
        </div>
    )
}
export default Funcom

创建多个状态呢

1.你写多个useState

import {useState} from "react"
let Funcom=()=>{
    // 1.你写多个useState了解
    let [xiaoming,setxiaoming]=useState("1")
    let [xiaohong,setxiaohong]=useState("2")
  
    return (
        <div>
          {xiaoming}---{xiaohong}
        </div>
    )
}
export default Funcom

2.一次行创建多个值

import {useState} from "react"
let Funcom=()=>{
    // 1.你写多个useState了解
    // let [xiaoming,setxiaoming]=useState("1")
    // let [xiaohong,setxiaohong]=useState("2")
  
    // 2.一次性创建多个值
        let [xiaoming,setxiaoming]=useState({
            dataa:"第一个值1",
            datab:"第一个值2",
            datac:"第一个值3",
            datad:"第一个值4",
            datae:"第一个值5",
            dataf:"第一个值6"
        })
    return (
        <div>
          {/* {xiaoming}---{xiaohong} */}

          {xiaoming.dataa}----{xiaoming.datad}
        </div>
    )
}
export default Funcom

一次性创建多个值怎么修改

1.多个useState 要修改的话就依次调用其中的修改方法

2.一次行创建多个值的方式如何修改呢

import {useState} from "react"
let Funcom=()=>{
    // 1.你写多个useState了解
    // let [xiaoming,setxiaoming]=useState("1")
    // let [xiaohong,setxiaohong]=useState("2")
  
    // 2.一次性创建多个值
        let [xiaoming,setxiaoming]=useState({
            dataa:"第一个值1",
            datab:"第一个值2",
            datac:"第一个值3",
            datad:"第一个值4",
            datae:"第一个值5",
            dataf:"第一个值6"
        })

       let fun=()=>{
        //    一次性创建多个的修改操作   不要忘了保留原始数据
        setxiaoming({...xiaoming,datad:"我被改了"})
       }

    return (
        <div>
          {/* {xiaoming}---{xiaohong} */}

          {xiaoming.dataa}----{xiaoming.datad}
          <button onClick={fun}>点我修改</button>
        </div>
    )
}
export default Funcom

总结state

state就是react中用来创建状态的 状态就是数据

在我们使用state的时候有两种情况 分别是函数组件和类组件

类组件 中使用state来进行状态的创建 在修改的时候必须调用setState来进行修改 因为setState是异步的 并且会自动触发render重新渲染 从而让状态数据改变之后 页面也跟着改变

函数组件:他有另外一个名字叫无状态组件 也就是说默认情况不能使用state状态 如果要使用 我们可以使用react16.8新增的一个特性叫HOOK中的useState来进行状态的创建 useState接收一个参数 并且返回数组长度为2的一个内容 第一个是保存的变量 第二个是修改的动作

8 事件

在react中事件的绑定 使用小驼峰命名法

例:onclick 在react中 onClick onchange 在react中 onChange

绑定完事件之后在调用函数的时候不加()不加() 因为加了函数就自动调用了

绑定完事件之后在调用函数的时候不加()不加() 因为加了函数就自动调用了

绑定完事件之后在调用函数的时候不加()不加() 因为加了函数就自动调用了

绑定完事件之后在调用函数的时候不加()不加() 因为加了函数就自动调用了

基本事件操纵

事件绑定 使用小驼峰命名法 鼠标左键点击事件 onclick------》onClick onchange------》onChange

修改this指向

想想函数中的this指向

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
       class Fun extends React.Component{
        fun(){
            console.log(this)//undefined
        }
        render(){
            return (
                <div>
                  <button onClick={this.fun}>点我</button>  
                </div>
            )
        }
       }
        ReactDOM.render(<Fun/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

this是 undefined 所以我们在使用setState等属性方法的时候就会报错

怎么解决呢?

方式1 bind()

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
       class Fun extends React.Component{
        fun(){
            console.log(this)
        }
        render(){
            return (
                <div>
                    {/*bind方式修改this*/}
                  <button onClick={this.fun.bind(this)}>点我</button>  
                </div>
            )
        }
       }
        ReactDOM.render(<Fun/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

方式2 通过创建箭头函数

在创建函数的时候创建一个箭头函数

方式3 在constructor中提前对事件进行绑定

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
       class Fun extends React.Component{
        constructor(props){
            super(props)
            // 提前绑定this
            this.fun=this.fun.bind(this)
        }

        fun(){
            console.log(this)
        }
        render(){
            return (
                <div>
                  <button onClick={this.fun}>点我</button>  
                </div>
            )
        }
       }
        ReactDOM.render(<Fun/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

方式4 将事件调用的写法改为箭头函数的形式

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
       class Fun extends React.Component{
       
        fun(){
            console.log(this)
        }
        render(){
            return (
                <div>
                    {/*使用箭头函数调用*/}
                  <button onClick={()=>{this.fun()}}>点我</button>  
                </div>
            )
        }
       }
        ReactDOM.render(<Fun/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

函数参数传递

因为在react中函数调用的时候不加() 那我我们如果要传递函数的实参怎么传递?

方式1

使用bind方式进行传递

 <button onClick={this.fun.bind(this,"我是实参1","我是实参2")}>点我传递函数实参</button>

方式2

使用箭头函数调用函数进行传递

<button onClick={()=>{this.funb(1111,2222)}}>点我传递实参2</button>

事件对象event

使用event

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
       class Fun extends React.Component{
       
         // 直接传值event即可得到事件对象
        fun=(event)=>{
             console.log("您在输入框中是",event.target.value)
        }
        render(){
            return (
                <div>
                    <h1>事件对象</h1>
                    <input type="text" onInput={this.fun}/> 
                </div>
            )
        }
       }
        ReactDOM.render(<Fun/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

阻止事件传播与默认行为

同原生js

阻止特定事件的默认行为:event.preventDefault()

立即停止事件在 DOM 层次中的传播,即阻止事件冒泡:event.stopPropagation()

总结

事件绑定的方式

如何修改this指向 记得越多越好

函数参数传递

事件对象与修饰符

9 props

props功能在于组件间通信(父子组件)

注意

Props对于使用它的组件来说,是只读的。一旦赋值不能修改。也就是说props的值是不可变的只能在渲染的时候传入无法动态赋值。

父子组件

组件之前的嵌套 就能形成最基本的父子组件

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
       class Zi extends React.Component{
        render(){
            return (
                <div>
                    <h1>子</h1>    
                </div>
            )
        }
       }
       class Fu extends React.Component{
        render(){
            return (
                <div>
                    <h1>父</h1>
                    <Zi/>    
                </div>
            )
        }
       }
        ReactDOM.render(<Fu/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

props基本使用

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
       class Zi extends React.Component{
        render(){
            return (
                <div>
                    {/*创建props属性*/}
                    <h1>子--{this.props.name}</h1>    
                    <h1>子--{this.props.age}</h1>    
                    <h1>子--{this.props.sex}</h1>    
                </div>
            )
        }
       }
       class Fu extends React.Component{
        render(){
            return (
                <div>
                    <h1>父</h1>
                    {/*
                        传递参数
                        props的本质是 标签的属性
                        组件就是自定义标签
                        所以可以在标签中进行传递参数
                    */}
                    <Zi name="xixi" age="18" sex="男"/>    
                </div>
            )
        }
       }
        ReactDOM.render(<Fu/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

props 进阶--批量传递

上面props的使用中

子组件:出现了大量的 重复代码 this.props

父组件:在传递props参数的时候 数据少不影响 但是数据多了之后 会导致很难看 并且 后期调用后台参数的时候 需要一个个的赋值 很麻烦

 <!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>Document</title>
     <script src="react.16.8.6.js"></script>
     <script src="react-dom.16.8.6.js"></script>
     <script src="babel.min.js"></script>
 </head>
 <body>
     <div id="demoDiv"></div>
     <script type="text/babel">
         
        class Zi extends React.Component{
         render(){
             // 使用解构快速取出对象的值
             let {name,age,sex}=this.props
             return (
                 <div>
                     
                     <h1>子--{name}</h1>    
                     <h1>子--{age}</h1>    
                     <h1>子--{sex}</h1>    
                 </div>
             )
         }
        }
        class Fu extends React.Component{
         render(){
             let obj={
                 name:"xixi",
                 age:18,
                 sex:"男"
             }
             return (
                 <div>
                     <h1>父</h1>
                     {/*
                         使用扩展运算符
                     */}
                     <Zi {...obj}/>    
                 </div>
             )
         }
        }
         ReactDOM.render(<Fu/>,document.getElementById("demoDiv"))
 
     </script>
 </body>
 </html>

props验证

我们之前可以给组件中传递任何数据类型的数据 但是有的时候我们需要限制只能传递指定的类型 那么这个时候我们就可以使用props验证来完成

注意

react 15.5之前Props 验证使用 propTypes,它可以保证我们在应用组件的时候可以正确的传递值

props验证只会在控制台给开发者一个错误的警告 不会影响页面的展示

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

    <!-- 引用prop-types库 -->
    <script src="./prop-types.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
       class Zi extends React.Component{
        render(){
            // 使用解构快速取出对象的值
            let {name,age,sex}=this.props
            return (
                <div>
                    
                    <h1>子--{name}</h1>    
                    <h1>子--{age}</h1>    
                    <h1>子--{sex}</h1>    
                </div>
            )
        }
       }

        //    props验证
        Zi.propTypes={
            name:PropTypes.number,//设置类型为string
            age:PropTypes.string.isRequired//设置为字符串类型  同时不能为空
        }

       class Fu extends React.Component{
        render(){
            let obj={
                name:"xixi",
                // age:18,
                sex:"男"
            }
            return (
                <div>
                    <h1>父</h1>
                    <Zi {...obj}/>    
                </div>
            )
        }
       }
        ReactDOM.render(<Fu/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

props默认值--defaultProps

设置props默认值之后 在没有传递数据的时候会自动使用设置好的默认值进行页面的显示

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

    <!-- 引用prop-types库 -->
    <script src="./prop-types.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
       class Zi extends React.Component{
        render(){
            // 使用解构快速取出对象的值
            let {name,age,sex}=this.props
            return (
                <div>
                    
                    <h1>子--{name}</h1>    
                    <h1>子--{age}</h1>    
                    <h1>子--{sex}</h1>    
                </div>
            )
        }
       }

        //    props验证
        Zi.propTypes={
            name:PropTypes.number,//设置类型为string
            age:PropTypes.string.isRequired//设置为字符串类型  同时不能为空
        }

        // 设置默认值
        Zi.defaultProps={
            sex:"我是默认值"
        }

       class Fu extends React.Component{
        render(){
            let obj={
                name:"xixi",
                // age:18,
                // sex:"男"
            }
            return (
                <div>
                    <h1>父</h1>
                    <Zi {...obj}/>    
                </div>
            )
        }
       }
        ReactDOM.render(<Fu/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

函数组件使用props

在函数组件中使用props和类组件 有所不同 类组件中使用this.props.xxx 但是在函数组件中需要把props当成一个函数的形参传递进入 才能正常使用

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

    <!-- 引用prop-types库 -->
    <script src="./prop-types.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
       let Zi=(props)=>{
        // 使用解构快速取出对象的值
        let {name,age,sex}=props
            return (
                <div>
                    
                    <h1>子--{name}</h1>    
                    <h1>子--{age}</h1>    
                    <h1>子--{sex}</h1>    
                </div>
            )
       }

        //    props验证
        Zi.propTypes={
            name:PropTypes.string,//设置类型为string
            age:PropTypes.string.isRequired//设置为字符串类型  同时不能为空
        }

        // 设置默认值
        Zi.defaultProps={
            sex:"我是默认值"
        }

       let Fu=()=>{
        let obj={
                name:"xixi",
                age:18,
                // sex:"男"
            }
            return (
                <div>
                    <h1>父</h1>
                    <Zi {...obj}/>    
                </div>
            )
       }
        ReactDOM.render(<Fu/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

总结:

props是组件用来接收外部传递进来的数据的一个技术 在ract中使用props有两种方式

函数组件 把props当成函数的形参传入即可直接使用

类组件 使用this.props.xxx的方式进行使用

在props中还有props验证 props验证在15。5版本之后 必须要单独引用一个库叫prop-type 引用后直接定义

props偶默认值的写法 defaultprops来定义默认值

10 ref

类组件

React提供的这个ref属性(不能在无状态组件上使用 ref 属性,因为它们没有实例 ref属性是需要有组件实例才能使用)表示为对组件真正实例的引用其实就是ReactDOM.render()返回的组件实例

ref 返回是真实的dom节点。

一句话总结: 标识组件内部的元素

方式1 字符串方式

使用ref属性 标识组件 在使用this.refs.xxx 获取真实dom节点

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

    <!-- 引用prop-types库 -->
    <script src="./prop-types.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component{
            fun=()=>{
                // 获取ref绑定的dom元素
                console.log(this.refs.demoInput.value)
            }
            render(){
                return (
                    <div>
                       {/*设置ref绑定dom元素*/} 
                        <input type="text" ref="demoInput"/>
                        <button onClick={this.fun}>点我得到输入框的值</button>    
                    </div>
                )
            }
        }
        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

当前方式在React官方已经不推荐使用 后续内容已经废弃了这种写法

当前方式在React官方已经不推荐使用 后续内容已经废弃了这种写法

因为字符串的方式 效率会有问题

方式2 回调函数方式

回调函数就是在dom节点或组件上挂载回调函数,函数的入参是dom节点,达到的效果与字符串形式是一样的,都是获取其引用。

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

    <!-- 引用prop-types库 -->
    <script src="./prop-types.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component{
            fun=()=>{
                // 获取ref绑定的dom元素
                console.log(this.inputDom.value)
            }
            render(){
                return (
                    <div>
                       {/*设置ref绑定dom元素*/} 
                        <input type="text" ref={(demo)=>{this.inputDom=demo}}/>
                        <button onClick={this.fun}>点我得到输入框的值</button>    
                    </div>
                )
            }
        }
        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

扩展问题

官网中给我们介绍说内联函数的方式 会在更新的时候执行两次

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

    <!-- 引用prop-types库 -->
    <script src="./prop-types.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component{
            state={
                name:"xixi"
            }
            fun=()=>{
              
                console.log(this.inputDom.value)
            }
            funb=()=>{
                this.setState({
                    name:"haha"
                })
            }
            render(){
                return (
                    <div>
                       {/*1.我们在绑定ref的回调中 添加console查看是否执行*/} 
                        <input type="text" ref={(demo)=>{this.inputDom=demo;console.log("回调函数方式")}}/>
                        <button onClick={this.fun}>点我得到输入框的值</button> 
                        
                        {/*2.添加按钮修改一个输入  让render重新调用 模拟更新效果*/}
                        <h1>{this.state.name}</h1>
                        <button onClick={this.funb}>点我修改数据</button>
                    </div>
                )
            }
        }
        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

大家会发现这种方式会执行两次 因为我们之前写的方式是内联函数的方式 所以不想执行两次 我们就不是用内联函数的方式 使用绑定函数的方式即可解决

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

    <!-- 引用prop-types库 -->
    <script src="./prop-types.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component{
            state={
                name:"xixi"
            }
            fun=()=>{
              
                console.log(this.inputDom.value)
            }
            funb=()=>{
                this.setState({
                    name:"haha"
                })
            }

            demoref=(demo)=>{
                this.inputDom=demo;
                console.log("回调函数方式");
            }
            render(){
                return (
                    <div>
                       {/*1.我们在绑定ref的回调中 添加console查看是否执行*/} 
                        <input type="text" ref={this.demoref}/>
                        <button onClick={this.fun}>点我得到输入框的值</button> 
                        
                        {/*2.添加按钮修改一个输入  让render重新调用 模拟更新效果*/}
                        <h1>{this.state.name}</h1>
                        <button onClick={this.funb}>点我修改数据</button>
                    </div>
                )
            }
        }
        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

注意

回调函数方式在工作中 使用哪一种影响都不大 只是让大家了解下其中的问题 在被问到的时候可以了解

方式3 createRef方式

React.createRef() 当被调用的时候会返回一个可以存储ref标识的dom 的一个容器

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

    <!-- 引用prop-types库 -->
    <script src="./prop-types.js"></script>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component{
            // 1.创建出用来存储refdom的容器
            // React.createRef() 当被调用的时候会返回一个可以存储ref标识的dom 的一个容器
            myRef=React.createRef()
         
            fun=()=>{
                // 3.使用
                console.log(this.myRef.current.value)
            }
        
            render(){
                return (
                    <div>
                        {/*2.绑定*/}
                        <input type="text" ref={this.myRef}/>
                        <button onClick={this.fun}>点我得到输入框的值</button> 
                     
                    </div>
                )
            }
        }
        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

函数组件使用ref

函数组件默认不能使用ref 所以我们需要使用react16.8新增的HOOK中的useRef帮助我们使用ref

useRef就是可以让函数组件使用ref的一个技术

import {useRef} from "react"
let Funcom=()=>{
//    1.创建出useRef
    let xiaoming=useRef(null)

    let fun=()=>{
        // 3.获取
        console.log(xiaoming.current.value);
    }
    return (
        <div>
            {/* 2.绑定使用 */}
          <input type="text" ref={xiaoming}/>
          <button onClick={fun}>点我得到输入框的值</button>
        </div>
    )
}
export default Funcom

##

11 收集表单数据

受控组件与非受控组件

两者都是呈现 并且收集HTML 表单元素数据的 React 组件。

非受控组件

非受控组件就是在react中使用Ref技术获取DOM中表单的数据

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
            inputDom=React.createRef()

            handleSubmit=(event)=>{
                event.preventDefault();
                alert('提交的名字: ' + this.inputDom.current.value);
         
            }

            render() {
                return (
                    <form onSubmit={this.handleSubmit}>
                        <label>
                            名字:
                            <input type="text"  ref={this.inputDom}/>
                        </label>
                        <input type="submit" value="提交" />
                    </form>
                );
            }
        }

        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,可以快速编写代码,同时可以减少你的代码量。

但是官方是这样说的 所以还是不太建议大家日常使用非受控组件

受控组件

在react中 表单中的输入数据 被state所存储并且管理的组件 就是受控组件

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
            state = { 
                    value: '' 
                };

            handleChange=(event)=> {
                this.setState({ value: event.target.value });
            }

            handleSubmit=(event)=>{
                alert('提交的名字: ' + this.state.value);
                event.preventDefault();
            }

            render() {
                return (
                    <form onSubmit={this.handleSubmit}>
                        <label>
                            名字:
                            <input type="text" value={this.state.value} onChange={this.handleChange} />
                        </label>
                        <input type="submit" value="提交" />
                    </form>
                );
            }
        }

        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

12 组件生命周期

函数组件默认不能使用生命周期 如果要使用 那么就要使用HOOK来完成

旧版本

挂载阶段

constructor 构造器 初始化 同一个组件对象只会创建一次 注意:不能在第一次挂载到页面之前,调用setState,为了避免问题,构造函数中严禁使用setState

componentWillMount 组件准备挂载 正常情况下,和构造函数一样,它只会运行一次 可以使用setState,但是为了避免bug,不允许使用,因为在某些特殊情况下,该函数可能被调用多次

render 组件渲染虚拟dom 返回一个虚拟DOM,会被挂载到虚拟DOM树中,最终渲染到页面的真实DOM中 render可能不只运行一次,只要需要重新渲染,就会重新运行 严禁使用setState,因为可能会导致无限递归渲染

componentDidMount 组件挂载完毕 只会执行一次 可以使用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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
            constructor(props){
                super(props)

                console.log("构造器 初始化")
            }
            componentWillMount = () => {
                console.log("组件准备挂载")
            }

            componentDidMount = () => {
                console.log("组件挂载完毕")
            }
            render() {
                console.log("组件渲染虚拟dom")
                return (
                    <div>
                        <h1>挂载阶段</h1>    
                        
                    </div>
                );
            }
        }

        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

更新阶段

componentWillReceiveProps 组件接收到一个新的props时被调用 组件接收到一个新的prop时被调用 初始化不执行

shouldComponentUpdate 判断组件是否要更新 指示React是否要重新渲染该组件,通过返回true和false来指定 不写情况下,react会自动添加 并且返回true

componentWillUpdate 组件更新之前 组件即将被重新渲染

componentDidUpdate 组件更新之后 往往在该函数中使用dom操作,改变元素,这里可以操作原生的dom

更新数据 三种情况

1.setState修改数据

shouldComponentUpdate--》componentWillUpdate--》render--》componentDidUpdate

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
            constructor(props){
                super(props)
                this.state={
                    text:"我是默认值xixi"
                }
            }
            update=()=>{
                this.setState({
                    text:"我变了"
                })
            }


            shouldComponentUpdate(){
                console.log("判断组件是否要更新")
                return true
            }
            
            componentWillUpdate(nextProps, nextState){
              console.log("组件更新之前")
            }

            componentDidUpdate = (prevProps, prevState) => {
                console.log("组件更新之后")
            }
            
       
            render() {
                console.log("render渲染dom")
                return (
                    <div>
                        <h1>更新阶段</h1>    
                        <h1>{this.state.text}</h1>
                        <button onClick={this.update}>点我修改</button>
                    </div>
                );
            }
        }

        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

2.forceUpdate()修改数据

forceUpdate 强制刷新:forceUpdate就是重新render。有些变量不在state上,当时你又想达到这个变量更新的时候,页面更新 那么这个时候就可以使用forceUpdate()

调用forceUpdate()会导致组件跳过shouldComponentUpdate()直接修改

componentWillUpdate--》render--》componentDidUpdate

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
            // 不在state上的数据
            text="我是不state上的数据"

            update=()=>{
                this.text="变了"
                this.forceUpdate()
            }

            // 不会执行判断组件是否要更新
            shouldComponentUpdate(){
                console.log("不会执行判断组件是否要更新")
                return true
            }
            
            componentWillUpdate(nextProps, nextState){
              console.log("组件更新之前")
            }

            componentDidUpdate = (prevProps, prevState) => {
                console.log("组件更新之后")
            }
            
       
            render() {
                console.log("render渲染dom")
                return (
                    <div>
                        <h1>fourceUpdate更新阶段</h1>    
                        <h1>{this.text}</h1>
                        <button onClick={this.update}>点我修改</button>
                    </div>
                );
            }
        }

        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

3.父组件更新的时候

componentWillReceiveProps --》shouldComponentUpdate--》componentWillUpdate--》render--》componentDidUpdate

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
         class Zi extends React.Component {
            componentWillReceiveProps = (nextProps) => {

                console.log("组件接收到一个新的prop时被调用。初始化不触发",nextProps)
            }
            
                render(){
                    return (
                        <div>
                            子组件--{this.props.title}
                        </div>
                    )
                }
              }
        
        class Fu extends React.Component {
            state={
                text:"我是父组件的数据"
            }
            fun=()=>{
                this.setState({
                    text:"父组件修改了"
                })
            }

            render() {
                
                return (
                    <div>
                        <h1>父组件</h1>
                        <button onClick={this.fun}>点我修改</button>
                        <Zi title={this.state.text}/>
                    </div>
                );
            }
        }

        ReactDOM.render(<Fu/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

卸载阶段--componentWillUnmount

componentWillUnmount 组件实例卸载 通常在该函数中销毁一些组件依赖的资源,比如计时器

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
            fun=()=>{
                ReactDOM.unmountComponentAtNode(document.getElementById("demoDiv"))
            }
            componentWillUnmount = () => {
              console.log("卸载了")
            }
            
            
            render() {
                return (
                    <div>
                        <h1>卸载阶段</h1>    
                        <button onClick={this.fun}>点我卸载</button>
                    </div>
                );
            }
        }

        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

新版本生命周期

新生命周期图

getDerivedStateFromProps(很少用)

从字面来解释 get读取Derived衍生State状态from来自于props

它让组件在 props 发生改变时更新它自身的内部 state

去除了componentWillMount,把componentWillReceiveProps 变成 getDerivedStateFromProps

getDerivedStateFromProps 当前钩子在初始化和数据修改的时候都会触发 它应返回一个状态对象来更新 state,如果返回 null 则不更新任何内容。

我们直接测试下:

<!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>Document</title>
    <script src="./js/react.16.8.6.js"></script>
    <script src="./js/react-dom.16.8.6.js"></script>
    <script src="./js/babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
            getDerivedStateFromProps() {
                 console.log("getDerivedStateFromProps")
            }
            render() {
                return (
                    <div>
                        <h1>新生命周期</h1>    
                
                    </div>
                );
            }
        }
        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

大家会发现出现如下警告:说当前的方法必须使用静态属性 static来实现

添加之后

<!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>Document</title>
    <script src="./js/react.16.8.6.js"></script>
    <script src="./js/react-dom.16.8.6.js"></script>
    <script src="./js/babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
           static getDerivedStateFromProps() {
                console.log("getDerivedStateFromProps")
            }
            
            render() {
                return (
                    <div>
                        <h1>新生命周期</h1>    
                
                    </div>
                );
            }
        }

        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

修改之后

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
            // 定义初始状态
            state={

            }
            static getDerivedStateFromProps() {
                console.log("getDerivedStateFromProps")

                // 返回状态对象 也可以返回null
                // 返回状态对象 也可以返回null
                return {
                    
                }
            } 
            render() {
                return (
                    <div>
                        <h1>新生命周期</h1>    
                
                    </div>
                );
            }
        }
        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

那么我们已经知道了可以返回一个对象 或者返回null

那么什么叫:它让组件在 props 发生改变时更新它自身的内部 state

首先他有两个形参 参数1传递进来的props 参数2自身的state

<!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>Document</title>
    <script src="./js/react.16.8.6.js"></script>
    <script src="./js/react-dom.16.8.6.js"></script>
    <script src="./js/babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
            // 定义初始状态
            state={
                text:"我是state"
            }
            static getDerivedStateFromProps(nextProps, prevState) {
                console.log("参数1传递进来的props",nextProps)
                console.log("参数2自身的state",prevState)

            
                return {
                   
                }
            } 
            render() {
                return (
                    <div>
                        <h1>新生命周期</h1> 
                        <h1>{this.state.text}</h1>   
                        <h1>{this.props.title}</h1>
                    </div>
                );
            }
        }
        ReactDOM.render(<Com title="我是title"/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

返回状态对象 如果和初始化状态重名 会被替换 不重名不影响

<!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>Document</title>
    <script src="./js/react.16.8.6.js"></script>
    <script src="./js/react-dom.16.8.6.js"></script>
    <script src="./js/babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
      
            state={
                text:"我是state"
            }
            static getDerivedStateFromProps(nextProps, prevState) {
                console.log("参数1传递进来的props",nextProps)
                console.log("参数2自身的state",prevState)

            
                return {
                    // 返回状态对象 如果和初始化状态重名 会被替换 不重名不影响
                    text:nextProps.title
                }
            } 
            render() {
                return (
                    <div>
                        <h1>新生命周期</h1> 
                        <h1>{this.state.text}</h1>   
                        <h1>{this.props.title}</h1>
                    </div>
                );
            }
        }
        ReactDOM.render(<Com title="我是title"/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

但是注意getDerivedStateFromProps不是必须要使用(因为大量使用会造成代码冗余 难以维护) 因为在constructor中也可以接受props 同样可以传递给state的初始值

小例子

getDerivedStateFromProps 当前钩子在初始化和数据修改的时候都会触发

它应返回一个状态对象来更新 state (返回一个状态对象来更新state怎么用呢?)

如果返回 null 则不更新任何内容(不更新就不讨论了)。

我想把一个状态数据在初始化 和修改之后都变成大写

<!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>Document</title>
    <script src="react.16.8.6.js"></script>
    <script src="react-dom.16.8.6.js"></script>
    <script src="babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
            // 定义初始状态
            state={
                text:"xixi",
                age:18
            }
            static getDerivedStateFromProps(nextProps, prevState) {
                console.log("getDerivedStateFromProps")

                // 返回状态对象 如果和初始化状态重名 会被替换 不重名不影响 
                return {
                    text:prevState.text.toUpperCase()
                }
            }

            fun=()=>{
                this.setState({
                    text:"haha"
                })
            }
            
            render() {
                return (
                    <div>
                        <h1>新生命周期--{this.state.text}---{this.state.age}</h1>    
                        <button onClick={this.fun}>点我修改state</button>
                    </div>
                );
            }
        }

        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

getSnapshotBeforeUpdate(很少用)

  • 替换componentWillUpdate函数,将参数返回并传递给componentDidUpdate周期函数

  • getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()

  • 此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。

<!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>Document</title>
    <script src="./js/react.16.8.6.js"></script>
    <script src="./js/react-dom.16.8.6.js"></script>
    <script src="./js/babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
      
            
            getSnapshotBeforeUpdate = () => {
              console.log("getSnapshotBeforeUpdate")
        
            }
          
            
            render() {
                return (
                    <div>
                        <h1>新生命周期</h1>   
                    </div>
                );
            }
        }
        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

加入之后会出现如下警告

原因是因为:getSnapshotBeforeUpdate()应与componentDidUpdate()一起使用。此组件仅定义getSnapshotBeforeUpdate()。所以不能单独使用需要配合其他钩子进行使用

加入componentDidUpdate 就不会出现问题了

<!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>Document</title>
    <script src="./js/react.16.8.6.js"></script>
    <script src="./js/react-dom.16.8.6.js"></script>
    <script src="./js/babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
      
            
            getSnapshotBeforeUpdate = () => {
              console.log("getSnapshotBeforeUpdate")
        
            }
          
           componentDidUpdate = (prevProps, prevState) => {
             console.log("componentDidUpdate")
           }
           
            
            render() {
                return (
                    <div>
                        <h1>新生命周期</h1> 
                       
                       
                    </div>
                );
            }
        }
        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

修改数据 触发当前钩子函数

<!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>Document</title>
    <script src="./js/react.16.8.6.js"></script>
    <script src="./js/react-dom.16.8.6.js"></script>
    <script src="./js/babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
            state={
                text:"我是默认值"
            }
            
            getSnapshotBeforeUpdate = () => {
              console.log("getSnapshotBeforeUpdate")
        
            }
          
           componentDidUpdate = (prevProps, prevState) => {
             console.log("componentDidUpdate")
           }
           
           fun=()=>{
            this.setState({
                text:"我修改了"
            })
           }
            
            render() {
                return (
                    <div>
                        <h1>新生命周期</h1> 
                       <h1>{this.state.text}</h1>
                       <button onClick={this.fun}>点我修改</button>
                       
                    </div>
                );
            }
        }
        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

但是会发现 钩子是顺利触发了 但是出现如下警告

出现问题的原因是因为 需要返回一个快照值 或者是一个null

 					getSnapshotBeforeUpdate = () => {
              console.log("getSnapshotBeforeUpdate")
            //   返回null
              return null
        
            }

返回值是null 那么这个钩子函数就没有作用了 那么什么是快照值呢?根据概念来猜测--此生命周期的任何返回值将作为参数传递给 componentDidUpdate()。 这个值可以在componentDidUpdate()的第三个参数接受 所以我们来试着return 传递下

<!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>Document</title>
    <script src="./js/react.16.8.6.js"></script>
    <script src="./js/react-dom.16.8.6.js"></script>
    <script src="./js/babel.min.js"></script>

</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
            state={
                text:"我是默认值"
            }
            
            getSnapshotBeforeUpdate = () => {
              console.log("getSnapshotBeforeUpdate")
            //   返回
              return "xixi"
        
            }
          
           componentDidUpdate = (prevProps, prevState,Snapshot) => {
             console.log("之前的props",prevProps)
             console.log("之前的state",prevState)
             console.log("Snapshot快照值",Snapshot)
           }
           
           fun=()=>{
            this.setState({
                text:"我修改了"
            })
           }
            
            render() {
                return (
                    <div>
                        <h1>新生命周期</h1> 
                       <h1>{this.state.text}</h1>
                       <button onClick={this.fun}>点我修改</button>
                       
                    </div>
                );
            }
        }
        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

小练习

需求:每一秒添加一个新闻 并且在数量一定的时候 页面产生滚动。

<!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>Document</title>
    <script src="./js/react.16.8.6.js"></script>
    <script src="./js/react-dom.16.8.6.js"></script>
    <script src="./js/babel.min.js"></script>
    <style>
        .container{
            height: 100px;
            width: 100px;
            background-color: pink;
            overflow: auto;
        }
        .item{
            height: 30px;
        }
    </style>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
           state={
            arr:[]
           } 

        //    每一秒自动往arr中添加一条数据
           componentDidMount = () => {
            let i=0;
             setInterval(() => {
                i++
                this.setState({arr:[i,...this.state.arr]})
             }, 1000);
           }
           

           render(){
            console.log(this.state.arr)
            return (
                <div className="container">
                    {
                        this.state.arr.map((v,i)=>{
                            return (
                                <div className="item" key={i}>{v}</div>
                            )
                        })
                    }
                </div>
            )
           }
        }
        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

但是每次添加新内容页面就会自动滚动 那么我如何让页面定在我们选中的位置呢?

1.给整体div添加一个ref方便进行dom查找

<div className="container" ref={(demo)=>{this.demodiv=demo}}>

2.在页面修改展示之前先得到当前这个页面整体的高度 就是类名container(scrollHeight它返回该元素的像素高度)

					    getSnapshotBeforeUpdate=()=>{
 
                return this.demodiv.scrollHeight
           }

3.其次在数据更新完成之后 让页面距离顶部的高度(scrollTop)加等于 当前页面高度减去 更新数据之前的高度

				componentDidUpdate = (prevProps, prevState,height) => {
                this.demodiv.scrollTop+=this.demodiv.scrollHeight-height
           }	

整体代码

<!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>Document</title>
    <script src="./js/react.16.8.6.js"></script>
    <script src="./js/react-dom.16.8.6.js"></script>
    <script src="./js/babel.min.js"></script>
    <style>
        .container{
            height: 100px;
            width: 100px;
            background-color: pink;
            overflow: auto;
        }
        .item{
            height: 30px;
        }
    </style>
</head>
<body>
    <div id="demoDiv"></div>
    <script type="text/babel">
        
        class Com extends React.Component {
           state={
            arr:[]
           } 

        //    每一秒自动往arr中添加一条数据
           componentDidMount = () => {
            let i=0;
             setInterval(() => {
                i++
                this.setState({arr:[i,...this.state.arr]})
             }, 1000);
           }
           

           getSnapshotBeforeUpdate=()=>{
 
                return this.demodiv.scrollHeight
           }

           componentDidUpdate = (prevProps, prevState,height) => {
                this.demodiv.scrollTop+=this.demodiv.scrollHeight-height
           }
           
           render(){
            console.log(this.state.arr)
            return (
                <div className="container" ref={(demo)=>{this.demodiv=demo}}>
                    {
                        this.state.arr.map((v,i)=>{
                            return (
                                <div className="item" key={i}>{v}</div>
                            )
                        })
                    }
                </div>
            )
           }
        }
        ReactDOM.render(<Com/>,document.getElementById("demoDiv"))

    </script>
</body>
</html>

总结

新的生命周期去掉了三个will钩子,分别是:

componentWillMount 、componentWillReceiveProps、componentWillUpdate

新的生命周期新增了两个钩子,分别是:

1.getDerivedStateFromProps(nextProps, prevState) 用来替代 componentWillReceiveProps()。

2.getSnapshotBeforeUpdate(prevProps, prevState)方法用来替代componentWillUpdate()。

13.cra(create-react-app)

近期 create-react-app更新了

他里面对node的版本有要求了 node的版本不能低于14了

注意:win7系统node的版本不能大于12 (需要更新系统)

安装

1.全局安装create-react-app

npm install -g create-react-app

查看版本 create-react-app --version

2.cd到指定文件夹下

3.创建项目

create-react-app 项目名         your-app 注意命名方式

注意:如果不想全局安装可以使用npx来进行项目的安装(临时安装)

npx create-react-app myapp 也可以实现相同的效果

这需要等待一段时间,这个过程实际上会安装三个东西

react: react的顶级库

react-dom: 因为react有很多的运行环境,比如app端的react-native, 我们要在web上运行就使用

react-dom

react-scripts: 包含运行和打包react应用程序的所有脚本及配置

出现下面的界面,表示创建项目成功

Success! Created your-app at /dir/your-app 
Inside that directory, you can run several commands:
npm start 
Starts the development server. 
npm run build
Bundles the app into static files for production.
npm test Starts the test runner. npm run eject Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back! We suggest that you begin by typing:

cd your-app 
npm start

Happy hacking!

4.cd到你创建的项目下

5.npm start 启动项目

文件结构

生成项目的目录结构如下:

├── README.md 使用方法的文档

├── node_modules 所有的依赖安装的目录

├── package-lock.json 锁定安装时的包的版本号,保证团队的依赖能保证一致。

├── package.json

├── public 静态公共目录

└── src 开发用的源代码目录

常见问题

npm安装失败

淘宝镜像

1.切换为npm镜像为淘宝镜像

 npm config set registry https://registry.npm.taobao.org 

yarn

2.使用yarn,如果本来使用yarn还要失败,还得把yarn的源切换到国内

yarn就是和npm一样 都是一个包管理工具

yarn是由 facebook推出的一个包管理工具

yarn安装:npm install -g yarn

yarn和npm 的对照表

功能npmyarn
初始化npm inityarn init
安装依赖npm installyarn install或者 yarn
新增依赖npm install --save xxxyarn add xxx
全局安装npm install -g xxxyarn global add xxx
同时下载多个npm install --save xx1 xx2yarn add xx1 xx2
删除依赖npm uninstall --save xxxyarn remove xxx

如果觉得yarn默认下载很慢 那么我们可以把yarn切换成淘宝镜像地址

yarn config set registry https://registry.npm.taobao.org/

3.如果还没有办法解决,请删除node_modules及package-lock.json然后重新执行 npm

install命令

4.再不能解决就删除node_modules及package-lock.json的同时清除npm缓存 npm cache

clean --force 之后再执行 npm install 命令

14 样式模块化

主要防止css命名空间的污染(在vue中可以通过scoped来进行解决样式污染)那么在react中怎么解决呢?

方式1 scss样式嵌套

下载 npm install --save sass-loader@10 node-sass@6

直接编写scss文件 然后在需要使用的组件内 使用 import “你的样式文件路径即可”

方式2 CSS Module 样式模块化

1.在样式文件与.css后缀名之间 加入module 例如:style.css --- > style.module.css

2.在所需要的组件中使用 import 起个名字 from “xxx.module.css地址”

3.在使用的dom中在className上面添加 模块名.名字即可生效

15 多行html 空标签

在react的组件中多行html必须有一个父容器包裹 所以通常我们使用div来进行包裹 但是有的时候这些div是多余的 会在页面生成很多无用的代码

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

class Demob extends React.Component {
	render() {
		return (
            // 多行标签必须有一个父容器包裹
            <div>
                <h1>你好我是一个标签</h1>
                <h1>你好我是一个标签</h1>
                <h1>你好我是一个标签</h1>
                <h1>你好我是一个标签</h1>
                <h1>你好我是一个标签</h1>
            </div>
          
        )
	}
}

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

空标签

空标签 在页面是不进行展示的 它的作用仅仅就是用来描述多行标签的一个包裹作用

写法1:

<></>

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

class Demob extends React.Component {
	render() {
		return (
            // 空标签
            <>
                <h1>你好我是一个标签</h1>
                <h1>你好我是一个标签</h1>
                <h1>你好我是一个标签</h1>
                <h1>你好我是一个标签</h1>
                <h1>你好我是一个标签</h1>
            </>
          
        )
	}
}

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

写法2:

Fragment空标签

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

class Demob extends React.Component {
	render() {
		return (
			// 空标签
			<React.Fragment>
				<h1>你好我是一个标签</h1>
				<h1>你好我是一个标签</h1>
				<h1>你好我是一个标签</h1>
				<h1>你好我是一个标签</h1>
				<h1>你好我是一个标签</h1>
			</React.Fragment>

		)
	}
}

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

16 组件传值

正传---props

函数组件

子组件

// 1.把props当成函数的形参传入
let Zi=(props)=>{
    return (
        <div>
            {/* 2.使用props来进行数据的展示 */}
            ziziziziziziz--{props.title}
        </div>
    )
}

export default Zi

父组件进行传递

import Zi from "./zi.jsx"

let Fu=()=>{
    return (
        <div>
            FFUFUFUFUFUFUF
            {/* 父组件传递 */}
            <Zi title="我是父组件的书"></Zi>
        </div>
    )
}

export default Fu

公司的写法

父组件

import Zi from "./zi.jsx"

let Fu=()=>{
    // 定义数据
    let obj={
        title:"你好么么哒!!!!!"
    }
    return (
        <div>
            FFUFUFUFUFUFUF
            {/* 父组件使用扩展运符传递 */}
            <Zi {...obj}></Zi>
        </div>
    )
}

export default Fu



子组件
import React from 'react'

// 1.形参传入props
export default function Zi(props) {

    let {title}=props

  return (
    <div>
        Zi
        <span>
            {title}
        </span>
      
    </div>
  )
}

类组件

1.子组件 this.props.xxx

import React, { Component } from 'react'

export default class zi extends Component {
  render() {
    return (
      <div>
          zi
          {/* 1.子组件定义props */}
          <h1>父组件的数据式-----{this.props.title}</h1>
      </div>
    )
  }
}

2.父组件传递

import React, { Component } from 'react'
import Zi from "./zi.jsx"
export default class fu extends Component {
  render() {
    return (
      <div>
          fu
          {/* 父组件给子组件传递数据 */}
        <Zi title="我式父组件的数据"></Zi>    
      </div>
    )
  }
}

公司写法

子组件中使用结构来优化代码

import React, { Component } from 'react'

export default class zi extends Component {
  render() {
    //   使用解构的方式见到了this。props的重复出现率
    let {title,age,name,sex,love}=this.props
    return (
      <div>
          zi
          {/* 1.子组件定义props */}
          <h1>父组件的数据式-----{title}</h1>
          <h1>父组件的数据式-----{age}</h1>
          <h1>父组件的数据式-----{name}</h1>
          <h1>父组件的数据式-----{sex}</h1>
          <h1>父组件的数据式-----{love}</h1>
        
      </div>
    )
  }
}

父组件使用扩展运算符传递数据

import React, { Component } from 'react'
import Zi from "./zi.jsx"
export default class fu extends Component {
  render() {
      let obj={
          title:"我是title",
          name:"我是name",
          age:18,
          sex:"男",
          love:"女"
      }
    return (
      <div>
          fu
          {/* 扩展运算符快速传递数据 */}
        <Zi {...obj}></Zi>    
      </div>
    )
  }
}

this.props.children

思考

在react组件调用中我们的开标前和关标签中能否插入内容 ?

不能 因为组件是一个完整的独立的个体 默认不能插入

this.props.children 他表示所有组件的子节点(默认写上没有任何作用 在组件被调用的时候 如果我们在他的开关标签中插入dom元素 那么this.props.chilren 就会接收并且显示)

逆向传值--使用props接收一个函数

子组件把数据给父组件

利用回调函数 父组件提供函数,子组件调用并且把数据当做函数的参数传入

子组件

import React, { Component } from 'react'

export default class zi extends Component {
  render() {
    return (
      <div>
          zizizizzizi
          {/* 1.逆向传值必须通过事件来触发 一个父组件传递过来的函数*/}
          <button onClick={this.props.demofun}>点我进行逆向传值</button>
      </div>
    )
  }
}

父组件

import React, { Component } from 'react'
import Zi from "./zi.jsx"
export default class fu extends Component {
    fufun=()=>{
        
    }
  render() {
    return (
      <div>
          fuffufufufuf
          {/* 2.父组件给子组件传递一个函数 */}
          <Zi demofun={this.fufun}></Zi>
        
      </div>
    )
  }
}

子组件

import React, { Component } from 'react'

export default class zi extends Component {
  render() {
    return (
      <div>
          zizizizzizi
          {/* 1.逆向传值必须通过事件来触发 一个父组件传递过来的函数*/}
          {/* 3.给函数进行实参的传递 */}
          <button onClick={this.props.demofun.bind(this,"我是子组件的数据")}>点我进行逆向传值</button>
      </div>
    )
  }
}

父组件

import React, { Component } from 'react'
import Zi from "./zi.jsx"
export default class fu extends Component {
    // 4.父组件设置形参接收子组件绑定的实参
    fufun=(text)=>{
            console.log("我是父组件的函数",text)
    }
  render() {
    return (
      <div>
          fuffufufufuf
          {/* 2.父组件给子组件传递一个函数 */}
          <Zi demofun={this.fufun}></Zi>
        
      </div>
    )
  }
}

同胞传值

Pubsub-js

react中默认是不能进行同胞传值的 如果我们要进行 那么必须依赖 pubsub-js(是-js 千万不要记错了)库来实现

1.npm install --save pubsub-js

2.抛出 在需要传递的组件中使用 Pubsub.publish(“自定义事件名”,"数据") publish创建自定义事件

import React, { Component } from 'react'
// 1.引用pubsub-js
import Pubsub from "pubsub-js"
export default class zia extends Component {
    fun=()=>{
        // 2.publish抛出自定义事件
        Pubsub.publish("zia","我是zia的数据么么哒!!!!!")
    }
  render() {
    return (
      <div>zia
          <button onClick={this.fun}>点我把数据传递到zib</button>

      </div>
    )
  }
}

3.接收 在需要接收数据的组件中使用Pubsub.subscribe("你监听的事件",()=>{})subscribe 监听自定义事件

import React, { Component } from 'react'
// 3.引用pubsub
import Pubsub from "pubsub-js"
export default class zib extends Component {
    constructor(){
        super()
        console.log("1.react初始化数据")
    }
    componentWillMount(){
        console.log("2.在渲染之前调用")
    }
    componentDidMount() { 
    //    接收   监听同胞传值的自定义事件
        // 回调函数的第一个形参是你监听的自定义事件的名字
        // 回调函数的第二个形参就是自定义事件上绑定的数据
    Pubsub.subscribe("zia",(a,b)=>{
        console.log(a)
        console.log(b)
    })

     }
  render() {
      console.log("3开始渲染")
    return (
      <div>zib</div>
    )
  }
}

状态提升--中间人模式

React中的状态提升概括来说,就是将多个组件需要共享的状态提升到它们最近的父组件

上.在父组件上改变这个状态然后通过props分发给子组件.

跨组件传值

context对象--上下文对象

react 组件间传递数据是通过 props 向下,是单向传递的,从父级一层一层地通过 props 地向下传递到子子孙孙,有的时候我们组件一层一层的嵌套多层,这样这种方式一层一层传递麻烦,如果想跃层传递,这就会用到 context

context:上下文对象

context很好的解决了跨组件传值的复杂度。可以快速的进行跨组件数据的传递。

想要使用context进行跨组件传值那么就要使用createContext()方法同时方法中给我们提供了两个对象:

Provider对象 生产者---->用来生产数据 Consumer对象 消费者---->用来使用数据

import React, { Component } from 'react'
// 1.创建context对象
const GlobalContext = React.createContext()

class Zia extends Component {
    render() {
        return (
            <div>
                我是第一个子组件
            </div>
        )
    }
}
class Zib extends Component {
    render() {
        return (

            <div>
                我是第2个子组件
                {/* 3.任意组件引入GlobalContext并调用context,使用GlobalContext.Consumer(消费者) */}

                <GlobalContext.Consumer>
                    {/* 4.在回调函数中直接使用生产者的数据 */}
                    {

                        (value) => {
                            return <h1>{value.name}</h1>
                        }
                    }
                </GlobalContext.Consumer>
            </div>

        )
    }
}
export default class fu extends Component {
    render() {
        return (
            // 2.在根组件件引入GlobalContext,并使用GlobalContext.Provider生产者
            // 并且使用value属性传入数据
            <GlobalContext.Provider value={{ name: "xixi", age: 18 }}>
                <div>
                    我是根组件
                    <Zia />
                    <Zib />
                </div>
            </GlobalContext.Provider>
        )
    }
}

redux

redux是什么?

redux就是一个javascript的状态管理工具 可以集中的管理react中多个组件的状态 让我们组件之间数据传递变得非常的简单

redux是一个第三方的 也就是说他可以在react中用 也可以在vue中进行使用

如果组件需要进行跨层级传值 传统的方式 就是组件一层层的进行逆向传值传递到他们最近的一个父组件身上 然后再一层层的进行正向传值

redux的三大原则

1.单一数据源 :整个项目的数据都被存储在一个store对象中

2.state是只读的:如果我们想改变state的数据 那么必须触发action里面的修改动作来执行修改

3.使用纯函数来进行修改:reducer就是一个函数 我们通过reducer中的state和action动作来进行数据的修改

redux使用

1.下载redux npm install --save redux

2.在项目的文件夹中创建一个store文件夹(容纳redux的代码)

3.在新建一个文件容纳基本代码

// 1.创建redux对象(先引用redux创建方法createStore)
import {createStore} from "redux"
// 6.创建创建state数据
let data={
    name:"xixi",
    age:18,
    sex:"男"
}
// 5.创建reducer 是一个方法  其中保存的就是redux中存储的变量和修改变量的方法
// state就是数据状态  
// action 修改上面数据状态的一些动作
// 7.把上面的数据传递给state
let reducer=(state=data,action)=>{
    // 8把state return
    return state
}
// 2.开始创建redux对象
// 4.把状态和修改状态的方法传递到初始化redux中
let store=createStore(reducer)
// 3.暴露redux对象
export default store

读取redux中的数据

store.getState().你要读取的数据

import React, { Component } from 'react'
// 1.引用redux
import store from "../store/index.js"
export default class demoa extends Component {
    // 2.把数据复制给组件的state中进行保存
    state={
        name:store.getState().name
    }

  render() {
    return (
      <div>
          <h1>redux的基本使用</h1>
          {/* 3.读取 */}
          <h1>使用数据---{this.state.name}</h1>
      </div>
    )
  }
}

基本数据修改

我们需要通过dispatch()来调用写在action中的修改动作

千万不要和vuex搞混了 因为在vuex中 dispatch触发action是进行异步操纵的触发器

但是但是 在redux中 dispatch触发 的这个action里面存储的是修改状态的动作

  fun=()=>{
        // 通过dispatch来修改数据
        // store.dispach({type:"你要触发的修改动作"})
        store.dispach({type:"USER_UP_NAME"})
    }

编写修改数据的动作

import {createStore} from "redux"

let data={
    name:"xixi",
    age:18,
    sex:"男"
}

let reducer=(state=data,action)=>{
    // 创建修改动作
    switch (action.type) {
        case "USER_UP_NAME":
            console.log({...state,name:"我变了"})
            return {...state,name:"我变了"}
            break;
    
        default:
            return state
            break;
    }
}
let store=createStore(reducer)

export default store

但是大家运行后发现 数据修改了 但是页面并没有发生改变

原因很简单 因为 虽然你数据变了 但是组件中的render并没有重新执行 那么页面当然不会修改了

subscribe() 监听redux state的状态 改变就会触发

import React, { Component } from 'react'
// 1.引用redux
import store from "../store/index.js"
export default class demoa extends Component {
    // 2.把数据复制给组件的state中进行保存
    state={
        name:store.getState().name
    }

    // 我们可以监控这redux中的state数据  如果redux中的数据改变了
    // 我重启读取下并且在触发组件中的render那么 页面的内容就会改变
    componentDidMount() { 
        
        store.subscribe(()=>{
            // 当redux中的state数据改变了 那么subscribe就会触发
            this.setState({
                name:store.getState().name
            })
        })

     }

    fun=()=>{
        // 通过dispatch来修改数据
        // store.dispach({type:"你要触发的修改动作"})
        store.dispatch({type:"USER_UP_NAME"})
    }

  render() {
    return (
      <div>
          <h1>redux的基本使用</h1>
          {/* 3.读取 */}
          <h1>使用数据---{this.state.name}</h1>
          <button onClick={this.fun}>点我修改上面的数据</button>
      </div>
    )
  }
}

合并reducer(把redux拆分成一个个的模块)

随着项目的体积越来越大 项目的state和修改的动作也会越来越多

1.新建一个reducer.js(就是一个合并工厂 把今后拆分的一个个的小模块合并起来)

2.新建一个文件夹modules 里面放置我们拆分的一个个的小模块

3.开始拆分 把原来写在一起的state和原来写在一起的动作拆分出来

// 里面存放的就是demoa的state数据和demoa的修改动作
let data={
    name:"xixi",
 
}

let demoam=(state=data,action)=>{
    // 创建修改动作
    switch (action.type) {
        case "USER_UP_NAME":
            console.log({...state,name:"我变了"})
            return {...state,name:"我变了"}
            break;
    
        default:
            return state
            break;
    }
}

export default demoam

4.开始合并reducers.js中进行

// reducer合并工厂中吧modules文件夹中多个小模块进行合并
// 1.把你要合并的所有模块引用进来
import demoam from "./modules/demoam.js"
import demobm from "./modules/demobm.js"
// 2.引用合并模块的方法
import {combineReducers} from "redux"
// 3.开始合并
let reducer=combineReducers({
    demoam,
    demobm
})
// 4.暴露
export default reducer

5.把合并好的模块 注入到redux实例中

import {createStore} from "redux"

// 引用合并好的reducer
import reducer from "./reducers.js"

let store=createStore(reducer)

export default store

大家会发现 我们合并好模块之后 在页面不显示数据了 因为我们把内容都合并成了模块所以要使用的时候

store.getState().模块名.xxxx

redux的数据执行流程

react-redux

react-redux 是一个专门为react开发的状态管理工具 而redux是第三方的

之前redux的写法 和react的耦合度太高 (在react中吧第三方的redux集成在项目里面 会造成代码的可读性太低)

react-redux 就可以简化我们在react中使用redux的复杂度

使用

1.下载 npm install --save react-redux 如果没有下载redux也要下

2.我们需要在项目的全局组件之上 设置Provider 发布者

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

import App from "./components/demob.jsx"

// 1.引用provider
import { Provider } from "react-redux"
import store from "./store/index.js"
ReactDOM.render(
  // 2.使用provider把redux对象传递到所有的子组件身上
  // 3.传入store对象
  <Provider store={store}>
    <App />
  </Provider>,

  document.getElementById('root')
);

2.设置组件与redux的连接

import React, { Component } from 'react'
// 1.引用react-redux给我们提供的连接方法
// connect是一个函数  当这个函数被调用的时候就是一个高阶组件
import {connect} from "react-redux"
class demob extends Component {
    add=()=>{
    
    }
    del=()=>{
    
    }
  render() {
    return (
      <div>
          demob
          <h1>读取redux存储的age</h1>
          <button onClick={this.add}>点我+1</button>
          <button onClick={this.del}>点我-1</button>
    </div>
    )
  }
}
// connect是一个函数  当这个函数被调用的时候就是一个高阶组件
// 第一个() 就是调用函数的语法
// 第二个() 就是高阶组件的语法
export default connect()(demob)

3.得到redux中的数据

import React, { Component } from 'react'
// 1.引用react-redux给我们提供的连接方法
// connect是一个函数  当这个函数被调用的时候就是一个高阶组件
import {connect} from "react-redux"
class demob extends Component {
    add=()=>{
    
    }
    del=()=>{
    
    }
  render() {
    return (
      <div>
          demob
          {/* 使用react-redux读取数据 this.props.state.模块名.xxx */}
          <h1>读取redux存储的age--{this.props.state.demobm.age}</h1>
          <button onClick={this.add}>点我+1</button>
          <button onClick={this.del}>点我-1</button>
    </div>
    )
  }
}
// connect是一个函数  当这个函数被调用的时候就是一个高阶组件
// 第一个() 就是调用函数的语法
// 第二个() 就是高阶组件的语法
// 形参的state今后就是redux中的数据
export default connect(state=>({state}))(demob)

修改

在组件中调用dispatch就可以直接完成修改操作

   add=()=>{
        // react-redux修改数据
        // 还是使用dispatch触发action的动作
        this.props.dispatch({type:"AGE_NUMBER_ADD_ONE",num:3})
    }

17. 路由

根据url的不同来切换对应的组件页面

路由可以实现spa单页面应用 一个项目只有一个完整的页面 我们通过切换页面的显示内容 已达到不刷新页面进行切换的效果

路由分类

react-router库

仅仅只包含了路由最基本的功能没有一些辅助的api 但是他轻量级

react-router-dom库

除了基本的路由功能以外 还有很多便捷性的api方便我们开发者实现路由功能

npm install --save react-router-dom@5

路由模式

HashRouter

HashRouter 哈希路由: 使用url的hash值

BrowerRouter

BrowserRouter 浏览器历史记录路由: 使用H5的history API

BrowserRouter和HashRouter的不同点

1.兼容性不同

  • BrowserRouter 因为使用了H5的history API,不兼容IE9及以下

  • HashRouter 因为使用了url的哈希值兼容性更好

2.表现形式不同

  • HashRouter 地址栏带 #, BrowserRouter则不带 #

3.刷新对路由state参数的影响

  • BrowserRouter无影响,路由state保存在history对象中

  • HashRouter有影响,刷新后导致路由state参数丢失

实现

1.下载 npm install --save react-router-dom@5

2.设置路由模式 index.js中设置

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

// 1.引用路由模式
import {HashRouter} from "react-router-dom"

ReactDOM.render(
  // 2.设置路由模式  包裹根组件
  <HashRouter>
    <xxxx></xxxx>
  </HashRouter>,

document.getElementById("root"))

3.开始设置路由页面 写在views或者pages

4.设置路由规则与出口 新建router文件夹在新建index.js

import React, { Component } from 'react'
// 1.把你要用的路由页面进行引用
import Home from "../views/home.jsx"
import Phone from "../views/phone.jsx"
import User from "../views/user.jsx"
import Shop from "../views/shop.jsx"
// 2-1 引用Route
import {Route} from "react-router-dom"

export default class index extends Component {
  render() {
    return (
      <>
        {/* 路由规则与路由出口 */}
        {/* <Route path="/你的路径" component={你要引用的组件}/> */}
        {/* 2-2.配置出口与规则 */}
        <Route path="/home" component={Home}/>
        <Route path="/shop" component={Shop}/>
        <Route path="/user" component={User}/>
        <Route path="/phone" component={Phone}/>
    

      </>
    )
  }
}

5 设置路由配置组件为根组件index.js中

import React from 'react';
import ReactDOM from 'react-dom';
// 引用路由配置组件
import Index from "./router/index.js"

// 1.引用路由模式
import {HashRouter} from "react-router-dom"

ReactDOM.render(
  // 2.设置路由模式  包裹根组件
  <HashRouter>
    {/* 注入路由组件 */}
    <Index></Index>
  </HashRouter>,

document.getElementById("root"))

路由导航

声明式

<关键字 to="去哪里"></关键字>

Link 就是一个组基本的路由导航

NavLink 处理基本路由跳转的功能以外 还添加了自动选中类名的设置 active类名

但是 如果这个active的类名 已经存在了怎么办?

修改navlink的选中类名 activeClassName="你要修改的类名"

注意

有的同学navlink可能在电脑上不加类名 如果出现这种问题 那么就不要用vscode内置的cmd打开项目 而是用外部的cmd启动项目即可解决

编程式

push方法在路由页面中跳转 this.props.history.push("/xxxx")

常见问题

如果编程式导航中跳转的话 那么会出现 push of undefined的错误

原因:

是因为编程式导航只能在被路由所管理的页面中进行使用(被管理的页面是指 在路由规则中配置过的页面)因为不是被路由所管理的页面就不具备路由所提供的 三个属性(location match history)

所以就不能使用this.props.history 所以就会报错

解决方式:

使用withRouter 高阶组件(HOC)来解决 因为通过withRouter 可以让不是被路由所管理的页面也具有路由跳转的三个属性

使用:

1.引用withRouter

import {withRouter} from "react-router-dom"

2.修改组件的export default 到最下面

3.使用withRouter来设置当前组件

export default withRouter(当前组件)

更多跳转方式

replace() 替换当前路径

goBack()后退

goForward()前进

二级多级路由

1.编写二级路由页面

2.配置规则与出口 只需要在对应的一级路由页面中进行route的配置

import React, { Component } from 'react'

import {Route,NavLink} from "react-router-dom"

import Era from "./er/era.jsx"
import Erc from "./er/erc.jsx"
export default class phone extends Component {
  render() {
    return (
      <div>
          phone
          <NavLink to="/phone/era">era</NavLink>
          <NavLink to="/phone/erc">erc</NavLink>
          
          {/* 配置二级路由规则与出口 */}
          <Route path="/phone/era" component={Era}/>
          <Route path="/phone/erc" component={Erc}/>
        </div>
    )
  }
}

404页面

1.创建页面

2.配置404页面规则

  <>
       {/* 路由导航的组件 */}
        <Bb></Bb>
        {/* 路由规则与路由出口 */}
        {/* <Route path="/你的路径" component={你要引用的组件}/> */}
        {/* 2-2.配置出口与规则 */}
        <Route path="/home" component={Home}/>
        <Route path="/shop" component={Shop}/>
        <Route path="/user" component={User}/>
        <Route path="/phone" component={Phone}/>


        {/* 404页面必须放在最下面 */}
        <Route component={No}/>

      </>

大家会发现当我们加入了404页面之后 每个页面都会把404显示出来 因为reacrt的路由在匹配到之后 还会一直向下进行 直到最后

Switch 唯一渲染

用switch包裹的内容会渲染上之后不继续向下进行 只会匹配到第一个匹配的内容

 {/* 唯一渲染 */}
        <Switch>
            {/* 2-2.配置出口与规则 */}
            <Route path="/home" component={Home}/>
            <Route path="/shop" component={Shop}/>
            <Route path="/user" component={User}/>
            <Route path="/phone" component={Phone}/>


            {/* 404页面必须放在最下面 */}
            <Route component={No}/>
        </Switch>

重定向与精准匹配

重新定位方向

exact 精准匹配 只能是指定的内容才能匹配

{/* 唯一渲染 */}
        <Switch>
            {/* 2-2.配置出口与规则 */}
            <Route path="/home" component={Home}/>
            <Route path="/shop" component={Shop}/>
            <Route path="/user" component={User}/>
            <Route path="/phone" component={Phone}/>

            {/* 重定向 */}
            {/* 精准匹配   exact */}
            <Redirect from="/" to="/home" exact/> 
            {/* 404页面必须放在最下面 */}
            <Route component={No}/>
    
        </Switch>

路由传参

params方式

1.配置路径

 <Route path="/home/:xxx" component={Home}>

2.发送

声明式

 <NavLink to="/phone/数据"></NavLink>

编程式

 this.props.history.push("/phone/数据")

3.接收

this.props.match.params.xxx

优势 : 刷新地址栏,参数依然存在

缺点 : 只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋。

query方式

1.发送

<Link to={{ pathname : '/d' , query : { name : 'sunny' }}}>点我去d</Link>


this.props.history.push({ pathname : '/d' , query : { name : 'sunny' }})

2.接受

this.props.location.query.xxx

优势:传参优雅,传递参数可传对象;

缺点:刷新地址栏,参数丢失

state方式

1.发送

<Link to={{ pathname : '/d' , state: { name : 'sunny' }}}>点我去d</Link>

2.接受

this.props.location.state.name

优势:传参优雅地址栏不显示传递的数据,传递参数可传对象;

缺点:刷新地址栏,参数丢失

路由拦截

render调用一个函数那么我们就可以决定什么时候渲染他 同时传入props那么就可以在路由组件中使用history: {…}, location: {…}, match: {…}这几个对象

    <Route path="/home" render={(props)=>{return <Home {...props}/>}}>

16 HOC--高阶组件

在React组件的构建过程中,常常有这样的场景,有一类功能需要被不同的组件公用。(React 中用于重用组件逻辑的高级技巧

HOC--参数是组件同时返回值也是组件

HOC不仅仅是一个方法,确切说应该是一个组件工厂,获取低阶组件(功能较少),生成高阶组件(添加多个组件复用的功能)

应用场景

需要代码重用时, react如果有多个组件都用到了同一段逻辑, 这时,就可以把共同的逻辑部分提取出来,利用高阶组件的形式将这段逻辑整合到每一个组件中, 从而减少代码的逻辑重复

例子:

比如在多个函数中我们都想使用一个相同的方法。这个方法就是自动发出一段异步请求 并且把请求成功的数据赋值给一个state在页面展示。

那么传统方式我们就需要把这个请求在每个组件中都是用一次 组件少了可以。但是组件变多就很麻烦 那么我们就需要想办法解决这个重复的代码 所以我们可以使用HOC来进行组件中公共内容的复用

大家发现下面的例子 这个请求可能会在多个组件都要使用 那么我们可以把他设置成一个HOC

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

export default class two extends Component {
    state={
        text:""
    }
//    发送请求
   async componentDidMount (){
      let {data}= await axios({url:"/api/data/cityinfo/101320101.html"})
    //   把数据赋值给state 并且在下方展示
      this.setState({
          text:data.weatherinfo.city
      })
    }
    
  render() {
    return (
      <div>
        {this.state.text}
      </div>
    )
  }
}

HOC创建

1.新建withxxx文件 用来容纳HOC(高阶组件明明规则 是withxxx 比如withRouter)

// HOC本质就是一个函数
export default ()=>{

}

2.使用HOC 在需要服用的组件中把刚才封装的HOC引用使用

import axios from 'axios'
import React, { Component } from 'react'
// 1.引用高阶组件
import withLink from "./withLink.js"
// 2.修改暴漏在最下面
class two extends Component {
    state={
        text:""
    }

   async componentDidMount (){
      let {data}= await axios({url:"/api/data/cityinfo/101320101.html"})

      this.setState({
          text:data.weatherinfo.city
      })
    }
    
  render() {
    return (
      <div>
        {this.state.text}
      </div>
    )
  }
}
// 3.把当前组件当成函数的实参传入进去(因为高阶组件参数是一个组件)
export default withLink(two)

3.在高阶组件中设置行参接受传递进来的组件并且返回一个组件

// HOC本质就是一个函数

import React from "react"

// 设置行参接受(首字母要大写)
export default (Component)=>{
    // 并且返回值函数一个组件(组件可以是函数组件也可以是类组件)
    return class withLinkComponent extends React.Component{

        render(){
            return (
            	<>
            
            	</>
            )
        }
    }
}

4.移植逻辑

// HOC本质就是一个函数

import React from "react"
import axios from "axios"

// 设置行参接受(首字母要大写)
export default (Component) => {
    // 并且返回值函数一个组件(组件可以是函数组件也可以是类组件)
    return class withLinkComponent extends React.Component {

        // 把之前的逻辑放置进来
        state = {
            text: ""
        }

        async componentDidMount() {
            let { data } = await axios({ url: "/api/data/cityinfo/101320101.html" })

            this.setState({
                text: data.weatherinfo.city
            })
        }
        // 把之前的逻辑放置进来

        render() {
            return (
                <>
                    {/* 把数据传递出去 */}
                    <Component {...this.state}></Component>
                </>
            )
        }
    }
}

组件中使用

import axios from 'axios'
import React, { Component } from 'react'
// 1.引用高阶组件
import withLink from "./withLink.js"
// 2.修改暴漏在最下面
class two extends Component {

    
  render() {
    return (
      <div>
          {/* 组件内使用高阶组件的数据 */}
        {this.props.text}
      </div>
    )
  }
}
// 3.把当前组件当成函数的实参传入进去(因为高阶组件参数是一个组件)
export default withLink(two)

高阶组件---反向继承

HOC的反向继承 说的简单点就是渲染劫持 在使用HOC的时候可以进行一个高阶组件中的条件渲染

15 react ajax

在前后端分离项目中,前端请求后端接口得到后端数据,完成页面内容的渲染或功能状态的判断,已经成为常规操作。那么,关于前端如何请求后端接口获取并解析数据,主要有哪些方式呢:

1.jquery ajax方式

jquery比较重 不建议在项目中使用

2.fetch

fetch 最新的前后台异步数据交互的方式 传统的方式 axios或者是原生 jqueryajax 都是基于XMLHttpRequest方法来进行实现的 但是fetch不是因为Es最新规范中给我们提供了fetchAPI通过这个fetch

Api来进行前后台的数据交互

import React, { Component } from 'react'

export default class demob extends Component {
    componentWillMount(){
        console.log("will")
    }
    componentDidMount() { 
        console.log("did")
        // fetch使用
        fetch("http://localhost:8888/one")
        // 把数据转换成json
        .then(ok=>ok.json())
        .then((ok)=>{
            console.log(ok)
        })

     }
  render() {
    return (
      <div>demob</div>
    )
  }
}

其他的方式

import React, { Component } from 'react'

export default class demob extends Component {
    componentWillMount(){
        console.log("will")
    }
    componentDidMount() { 
        console.log("did")
        // fetch使用
        fetch("http://localhost:8888/one")
        // 把数据转换成json
        .then(ok=>ok.json())
        .then((ok)=>{
            console.log(ok)
        })

        // 发送数据get
        fetch("url/?key=val&key=val",{method:"GET"})
        // 把数据转换成json
        .then(ok=>ok.json())
        .then((ok)=>{
            console.log(ok)
        })

        // 发送post
        fetch(
            "url",{
                method:"POST",
                body:"key=val&key=val"
            }
        )
        // 把数据转换成json
        .then(ok=>ok.json())
        .then((ok)=>{
            console.log(ok)
        })

     }
  render() {
    return (
      <div>demob</div>
    )
  }
}

3.axios

axios 是目前最优秀的 HTTP 请求库之一,虽然 axios 已经封装的非常好了,我们可以直接拿过来用。但是在实际的项目中,我们可能还需要对 axios 在封装一下,以便我们更好的管理项目和各个接口。

axios常见api

axios.request

该方法是axios项目的核心处理方法,实现用户自定义配置、应用拦截器、发送请求核心功能

axios其他api

就是发送相关请求 比如get请求 delete请求等

get方式--params发送参数

    //   axios.get("请求地址",{params:{发送数据key:发送的val}}).then((ok)=>{
    //     // 成功回调
    //   }).catch((err)=>{
    //     // 失败回调
    //   })
      axios.get("/api/userlist/get",{params:{name:"xixi"}}).then((ok)=>{
        console.log(ok)
      }).catch((err)=>{
        console.log(err)
      })
    }

post方式--data发送参数

		//   axios.post("请求地址",{data:{发送的key:发送的val}}).then((ok)=>{
    //     // 成功回调
    //   }).catch((err)=>{
    //     // 失败回调
    //   })
      axios.post("/api/userlist/post",{data:{name:"xixi"}}).then((ok)=>{
        console.log(ok)
      }).catch((err)=>{
        console.log(err)
      })

但是大家会发现后台接收不到我们发送的数据

原因是因为:

在发送post的时候Content-type(表示请求和响应中的媒体类型信息。它用来告诉服务端如何处理请求的数据,以及告诉客户端(一般是浏览器)如何解析响应的数据)常见有三种形式:

  • Content-Type: application/json 就是给后台的数据是一个json对象

  • Content-Type: application/x-www-form-urlencoded 表单数据编码为键值对,&分隔 如:name=java&age = 23

  • Content-Type: multipart/form-data 通常文件上传

现在最主流的是application/json形式axios默认就是这种方式 就像上面我们写的post代码 直接把后该要的参数放到data中就可以了

如图可以发现我们的请求方式

但是有时候后端要求Content-Type必须以application/x-www-form-urlencoded形式,那么通过上面application/json传递的参数,后端是收不到的,我们必须对参数数据进行所谓的序列化处理才行,让它以普通表单形式(键值对)发送到后端,而不是json形式

用qs模块来序列化参数

我们也能通过第三方依赖来序列化参数,就更加方便简洁,下载qs模块。

1.下载 npm install --save qs

2.引用 import qs from “qs”

3.在传递数据的时候使用qs序列化

let key=qs.stringify({
            key:val
})

import React, { Component } from 'react'
import axios from "axios"
// 引用qs
import qs from 'qs';
export default class demo extends Component {
    componentDidMount = () => {
    // 序列化数据
    let key=qs.stringify({
        name:"xixi"
    })
        // 传递
      axios.post("/api/userlist/post",key).then((ok)=>{
        console.log(ok)
      }).catch((err)=>{
        console.log(err)
      })
    }
    
  render() {
    return (
      <div>
        
      </div>
    )
  }
}

delete put 等方式

delete方式 同get

 axios.delete("/api/userlist/delete",{params:{name:"xixi"}}).then((ok)=>{
        console.log(ok)
      }).catch((err)=>{
        console.log(err)
      })

put方式 同post

import React, { Component } from 'react'
import axios from "axios"
// 引用qs
import qs from 'qs';
export default class demo extends Component {
    componentDidMount = () => {
    // 序列化数据
    let key=qs.stringify({
        name:"xixi"
    })
        // 传递
      axios.put("/api/userlist/put",key).then((ok)=>{
        console.log(ok)
      }).catch((err)=>{
        console.log(err)
      })
    }
    
  render() {
    return (
      <div>
        
      </div>
    )
  }
}

跨域

方式1

同vue一样也是对devServer进行代理跨域 但是唯一的不同的写跨域的文件不一样

需要到node_modules/react-scripts/config/webpackDevServer.config.js中进行配置

找到proxy 进行设置

proxy:{
        "/api(可以随便写)":{
             target:"请求地址",
             changeOrigin:true,
             "pathRewrite":{
               "^/api(和上面一样)":"/"
             }
        }
    },

但是不要忘了修改请求的地址为/api 与重启项目

方式2:http-proxy-middleware

1.下载:npm install http-proxy-middleware --save

2.在项目的src路径下创建setupProxy.js

3.写入如下内容

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://www.weather.com.cn/',
      changeOrigin: true,
      pathRewrite:{
        "^/api":"/"
      }
    })
  );
};

4.修改组件的请求为/api

axios怎么封装

在大型项目中 http请求会有很多 而且我们需要区分 所以我们可以把数据请求拆分出来 单独管理这样一来 就可以增强项目的可维护性与可扩展性

大体分为如下内容

  1. api 请求集中式管理

  2. 实现请求拦截

  3. 实现响应拦截

  4. 常见错误信息处理

  5. 请求头设置

api请求集中管理

1.初始化axios实例 编写util文件夹 在编写index.js来容纳封装内容

虽然axios中已经给我们封装好了一些常见的api如上面的axios.get axios.post等

但是我们为了更好的全局控制所有请求的相关配置,所以我们使用 axios.create()创建实例的方法来进行相关配置,这也是封装 axios 的精髓所在。

通过 create() 方法我们得到了一个 axios 的实例,该实例上有很多方法,比如拦截器等等。我们创建实例的时候可以配置一些基础设置,比如基础请求地址,请求超时等等。

  import axios from "axios"
  // 创建 axios 请求实例
  const serviceAxios = axios.create({
      baseURL: "", // 基础请求地址
      timeout: 10000, // 请求超时设置
  });
  export default serviceAxios

2.新建api文件夹 并且编写js文件用来容纳 api请求集中管理

import request from "../util/index.js"

export let funget=()=>{
    return request({
        url:"/api/userlist/get",
        method:"GET",
        params:{
            name:"xixi"
        }
    })
}

3.在组件中进行使用我们封装的 api请求集中管理

import React, { Component } from 'react'
// 引用
import {funget} from "../api/index.js"

export default class demo extends Component {
    componentDidMount = () => {
        // 使用
        funget().then((ok)=>{
            console.log(ok)
        })
    }
  render() {
    return (
      <div>
        
      </div>
    )
  }
}

我们就简单的划分出 API 管理层了,这样我们每次新增加一个 API,只需要找到对应模块的 API 文件去添加即可,然后再到具体页面导入使用就行啦。

添加拦截器

添加请求 响应拦截

import axios from "axios"
// 创建 axios 请求实例
const serviceAxios = axios.create({
    baseURL: "", // 基础请求地址
    timeout: 10000, // 请求超时设置

});
// 添加请求拦截器
serviceAxios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});

// 添加响应拦截器
serviceAxios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
}, function (error) {
    // 对响应错误做点什么
    return Promise.reject(error);
});



export default serviceAxios

响应拦截设置HTTP状态码

得到错误http状态码

// 添加响应拦截器
serviceAxios.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
}, function (error) {
    // 对响应错误做点什么

    // 得到错误http状态码
    console.log("响应",error.response.status)
    return Promise.reject(error);
});

// 添加响应拦截器
service.interceptors.response.use(function (response) {
    // 对响应数据做点什么
    return response;
}, function (error) {
    console.log("error", error.response.status)
    switch (error.response.status) {
        case 302: alert('接口重定向了!'); break;
        case 400: alert('参数不正确!'); break;
        case 401: alert('您未登录,或者登录已经超时,请先登录!'); break;
        case 403: alert('您没有权限操作!'); break;
        case 404: alert('请求地址出错'); break; // 在正确域名下
        case 408: alert('请求超时!'); break;
        case 409: alert('系统已存在相同数据!'); break;
        case 500: alert('服务器内部错误!'); break;
        case 501: alert('服务未实现!'); break;
        case 502: alert('网关错误!'); break;
        case 503: alert('服务不可用!'); break;
        case 504: alert('服务暂时无法访问,请稍后再试!'); break;
        case 505: alert('HTTP版本不受支持!'); break;
        default: alert('异常问题,请联系管理员!'); break
    }
    // 对响应错误做点什么
    return Promise.reject(error);
});

设置请求拦截请求头信息

你把用户的token以请求头的方式给后台

地址:/userlist/routertoken

方式 get

参数: 头信息是usertoken

返回值??

fetch VS axios VS ajax区别

1.传统的ajax 就是值使用XMLHttpRequest方法实现的数据请求 他隶属于原生的js 核心就是XMLHttpRequest对象 如果多个请求有先后顺序的话 那么容易造成回调地狱问题

jqueryajax 就是对原生XMLHttpRequest封装

2.axios 是基于promise封装的 本质上还是XMLHttpRequest的封装 只不过他是基于最新的语法进行封装的

3.fetch 就是原生js最新标准 和XMLHttpRequest没有半点关系

弹射

能不弹就不要弹 在弹射之前确定好你在弹 因为弹射出现的公司任何问题本人不负责

弹射不可逆 (弹射就是把原来配置文件隐藏的层级很深 现在弹到根路径下)

npm run eject 可能会出现错误 让你使用git提交完再次弹射

扩展---umi

Umi,中文可发音为乌米,是可扩展的企业级前端应用框架。Umi 以路由为基础的(就是解决了之前使用react-router-dom的时候使用路由的复杂度)

安装

1.全局安装 : npm install -g umi@2 / yarn global add umi@2

2.测试安装版本: umi -v

3.新建文件夹 把cmd的路径切换到这个文件夹下

4.开始创建项目 yarn create @umijs/umi-app / npm create @umijs/umi-app

5.下载依赖:cd到你刚才创建项目的文件夹下 npm install / yarn

6.运行 yarn start npm start

配置式路由的创建

1.在src下的pages文件夹下 新建你对应的路由页面

2.配置路由规则 在.umirc.ts文件中进行配置

import { defineConfig } from 'umi';

export default defineConfig({
  nodeModulesTransform: {
    type: 'none',
  },
  routes: [
    { path: '/', component: '@/pages/index' },
    // 仿照默认的内容自行添加
    { path: '/home', component: '@/pages/home/home' },
    { path: '/phone', component: '@/pages/phone/phone' },
  ],
  fastRefresh: {},
});

路由导航

编程式--js的方式

history.push("去哪里")

import React from 'react'
// 注意引用是来自于umi的
import {Link,history} from "umi"
export default function Topbar() {
    let fun=()=>{
        // 编程式导航
        history.push("/phone")
    }
  return (
    <div>
        <Link to="/">首页</Link>
        <Link to="/home">home</Link>
        <Link to="/phone">手机</Link>
        <hr />
        <button onClick={fun}>点我去手机</button>

    </div>
  )
}

声明式--标签

Link 这个标签来完成 使用to属性来表明路径

import React from 'react'
// 注意引用是来自于umi的
import {Link} from "umi"
export default function Topbar() {
  return (
    <div>
        <Link to="/">首页</Link>
        <Link to="/home">home</Link>
        <Link to="/phone">手机</Link>

    </div>
  )
}

重定向 --redirect

umirc文件里面进行配置

import { defineConfig } from 'umi';

export default defineConfig({
  nodeModulesTransform: {
    type: 'none',
  },
  routes: [
    { path: '/index', component: '@/pages/index' },
    // 仿照默认的内容自行添加
    { path: '/home', component: '@/pages/home/home' },
    { path: '/phone', component: '@/pages/phone/phone' },
    // 路由重定向    exact精准匹配
    { path: '/', redirect:"/index",exact:true },
  ],
  fastRefresh: {},
});

404页面

1.创建对应404页面

2.配置404规则

import { defineConfig } from 'umi';

export default defineConfig({
  nodeModulesTransform: {
    type: 'none',
  },
  routes: [
    { path: '/index', component: '@/pages/index' },
    // 仿照默认的内容自行添加
    { path: '/home', component: '@/pages/home/home' },
    { path: '/phone', component: '@/pages/phone/phone' },
    // 路由重定向    exact精准匹配
    { path: '/', redirect:"/index",exact:true },

    // 404页面
    { path: '*', component: '@/pages/no/no' },
  ],
  fastRefresh: {},
});

多级路由

1.创建多级路由页面

2.配置多级路由规则(在对应的以及路由规则中进行嵌套)

 { 
      path: '/home', 
      component: '@/pages/home/home',
      // 配置多级路由
      routes:[
        { path: '/home/era', component: '@/pages/er/era' },
        { path: '/home/erc', component: '@/pages/er/erc' },
      ]
    
    },

3千万不要忘了 设置二级路由出口 (在对应的以及路由页面中配置)props.children 多级路由的出口

3千万不要忘了 设置二级路由出口 (在对应的以及路由页面中配置)props.children 多级路由的出口

3千万不要忘了 设置二级路由出口 (在对应的以及路由页面中配置)props.children 多级路由的出口

3千万不要忘了 设置二级路由出口 (在对应的以及路由页面中配置)props.children 多级路由的出口

3千万不要忘了 设置二级路由出口 (在对应的以及路由页面中配置)props.children 多级路由的出口

3千万不要忘了 设置二级路由出口 (在对应的以及路由页面中配置)props.children 多级路由的出口

3千万不要忘了 设置二级路由出口 (在对应的以及路由页面中配置)props.children 多级路由的出口

3千万不要忘了 设置二级路由出口 (在对应的以及路由页面中配置)props.children 多级路由的出口


约定式路由

umi在创建路由的时候 我们也可以不需要配置路由规则 从而达到 路由效果 那么这种方式就叫约定式

创建页面两种方式

1.手工创建 同原来

2.吊的方式 命令创建

在项目的根路径下 输入 umi g page 你要创建的页面名 (就会在pages文件夹下 自动创建对应的页面)

扩展---dva

dva 首先是一个基于 reduxredux-saga 的数据流方案,然后为了简化开发体验 (dva就是对react进行了语法上面的简化 让我们在react中使用状态管理工具变得更简单)

redux-saga 就是redux进行异步操作的一个技术

使用

1.全局安装 npm install -g dva-cli

2.测试 dva -v

3.cd到指定文件夹之下 dva new 你的项目名

4.cd到项目下面

5.启动 npm start

路由

在dva中使用他内置的路由我们可以把路由页面创建到src下的routes文件夹中 当然我们也可以重新创建一个文件夹来容纳路由页面

1.创建路由页面

2.配置路由规则 src文件夹下的router.js文件中来进行编写

import React from 'react';
import { Router, Route, Switch } from 'dva/router';
import IndexPage from './routes/IndexPage';
// 1.引用路由页面
import Home from "./routes/home.jsx"
import Phone from "./routes/phone.jsx"
import User from "./routes/user.jsx"

function RouterConfig({ history }) {
  return (
    <Router history={history}>
      <Switch>
        <Route path="/" exact component={IndexPage} />
        {/* 2.配置路由规则 */}
        <Route path="/home" exact component={Home} />
        <Route path="/phone" exact component={Phone} />
        <Route path="/user" exact component={User} />
      </Switch>
    </Router>
  );
}

export default RouterConfig;

路由导航

编程式--js

props.history.push("/去哪里") 注意 使用编程式导航必须 实在被路由所管理的页面中进行才可以 否则就要使用withRouter这个高阶组件来传递路由跳转的属性

import React from 'react'

// 引用Link组件来自于dva 的路由
import {Link,withRouter} from "dva/router"

function topbar(props) {
    let fun=()=>{
        // 编程式导航\
        // 在组件中定义了一个编程式导航
        // 但是在运行的时候发现push没有定义
        // 因为 在使用编程式导航的时候   由于当前组件不是被路由所管理的页面  那么他就不具备路由跳转的功能
        // 就会出现push没有定义

        // 所以我们就要使用HOC--高阶组件--withRouter来解决
        props.history.push("/user")
    }
  return (
    <div>
        
        {/* 声明式导航 */}
        <Link to="/">首页</Link>
        <Link to="/home">home</Link>
        <Link to="/phone">phone</Link>
        <Link to="/user">user</Link>

        {/* 编程式 */}
        <button onClick={fun}>点我去user</button>

    </div>
  )
}

export default withRouter(topbar)

声明式--标签组件

Link

import React from 'react'

// 引用Link组件来自于dva 的路由
import {Link} from "dva/router"

export default function topbar() {
  return (
    <div>
        
        {/* 声明式导航 */}
        <Link to="/">首页</Link>
        <Link to="/home">home</Link>
        <Link to="/phone">phone</Link>
        <Link to="/user">user</Link>

    </div>
  )
}

model

dva 通过 model 的概念把一个领域的模型管理起来,包含同步更新 state 的 reducers,处理异步逻辑的 effects,订阅数据源的 subscriptions 。(dva中的model 就是用来把状态管理工具进行模块化 设置的 为什么要设置模块化 因为组件多了 数据与修改就变得很多 如果我们把一个项目的所有数据与修改都放在一起 后期维护就很麻烦 所以这个时候我们就可以 进行模块拆分)

1.在src的modules文件夹中 创建对应的模块文件

2.编写内容

export default {

    namespace: 'userm',//命名空间---就是给当前这个模块起个名字(唯一的)
  
    state: {
        name:"我是user模块中的数据"
    },//存放数据的
  
    subscriptions: {//类似于一个监听  在里面可以绑定很多个监听的方案
    
    },
  
    effects: {//类似于vuex的actions  进行异步触发的
    
    },
  
    reducers: {//  修改上面的state的地方
     
    },
  
  };
  

3.把我们新建的模块和项目建立关联

在srtc下的index.js中建立

import dva from 'dva';
import './index.css';

// 1. Initialize
const app = dva();

// 2. Plugins
// app.use({});

// 3. Model  就是建立我们模块与项目的位置
// app.model(require('./models/example').default);
app.model(require('./models/homem').default);
app.model(require('./models/phonem').default);
app.model(require('./models/userm').default);

// 4. Router
app.router(require('./router').default);

// 5. Start
app.start('#root');

读取数据

import React from 'react'
import Topbar from "../components/topbar.jsx"
// 1.引用组件和模块的链接工具--connect  是一个方法  当这个方法被调用的时候他才是一个链接的高阶组件
import { connect } from 'dva'

function Home(props) {
  return (
    <div>
        <Topbar></Topbar>
        H
        {/* props.state.你要引用的模块的命名空间名字.数据名 */}
        <h1>我想读取dva中模块的数据----{props.state.homem.name}</h1>
        <h1>我想读取dva中另一个模块的数据----{props.state.userm.name}</h1>
    </div>
  )
}
// 2.设置数据
export default connect(state=>({state}))(Home)

修改数据

1.在组件内通过dispatch来触发修改

import React from 'react'
import Topbar from "../components/topbar.jsx"
import { connect } from 'dva'
 function Phone(props) {
    let fun=()=>{
        // 触发dva的数据修改
        // dispatch({type:模块的命名空间名字/你的reducer的名字})
        props.dispatch({type:"phonem/UP_NAME"})
    }
  return (
    <div>
        <Topbar></Topbar>
        phone----{props.state.phonem.name}
        <button onClick={fun}>点我修改数据</button>
    </div>
  )
}
export default connect(state=>({state}))(Phone)

2.编写对应修改的reducers

export default {

    namespace: 'phonem',//命名空间---就是给当前这个模块起个名字(唯一的)
  
    state: {
        name:"我是phone模块中的数据"
    },//存放数据的
  
    subscriptions: {//类似于一个监听  在里面可以绑定很多个监听的方案
    
    },
  
    effects: {//类似于vuex的actions  进行异步触发的
    
    },
  
    reducers: {//  修改上面的state的地方
        // state:就是上面的数据源
        // payload就是接收dispatch的第二个参数
        UP_NAME(state,payload){
            return {...state,name:"我吧phone的数据修改了"}
        }
    },
  
  };
  

effects--异步触发

扩展-Generator

在dva中如果我们要使用effects来进行异步触发 那么我们需要使用es6中新特性之 Generator

Generator 函数 就是一个在es6中新出的一个特性 他的作用就是让我们的函数可以根据我们开发者的需求走走停停

观察:

<!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>Document</title>
</head>
<body>
    <script>
        // Generator 函数 就是一个在es6中新出的一个特性   他的作用就是让**我们的函数可以根据我们开发者的需求走走停停**

        // 观察如下代码  大家会发现当这个函数被调用的时候   函数体里面的内容  会全部自动执行完毕
        function fun(){
            console.log(111);
            console.log(222);
            console.log(333);
        }
        fun()
    </script>
</body>
</html>

思考:

我们能不能限制函数中的代码执行(根据我们的需要让函数体里面的东西执行或者暂停)

那么就可以使用generator函数 :

1.如果我们想控制函数的执行或者暂停 那么我们需要把函数变成generator函数 就是在function关键字 与函数名之间加一个*号

    function *fun(){
            console.log(111);
            console.log(222);
            console.log(333);
        }

2.我们需要让函数根据我们的需要走走停停 那么我们就必须在函数中加入表示 来表明暂停的位置是哪里 yield(就是函数暂停的分割线)

   function *fun(){
            console.log(111);
            yield
            console.log(222);
            yield
            console.log(333);
        }

3.我们在调用的时候 还需要告诉当前的generator函数 你要执行 next()

    function *fun(){
            console.log(111);
            yield
            console.log(222);
            yield
            console.log(333);
        }
        // 我们要执行函数中的内容
        let funger=fun()
        // 想让函数执行
        funger.next()
        funger.next()

effect使用

1.在组件中还是使用dispatch来触发

import Topbar from "../components/topbar.jsx"
import {connect} from "dva"
 function User(props) {
    let fun=()=>{
        // 触发effects进行异步操作
        // props.dispatch({type:"你要调用的模块命名空间名字/effect的名字"})
        props.dispatch({type:"userm/LINK"})
    }
  return (
    <div>
        <Topbar></Topbar>
        user
        
       <button onClick={fun}>点我触发异步操作</button> 
    </div>
  )
}
export default connect()(User)

2.编写effects异步触发器

export default {

    namespace: 'userm',//命名空间---就是给当前这个模块起个名字(唯一的)
  
    state: {
        name:"我是user模块中的数据"
    },//存放数据的
  
    subscriptions: {//类似于一个监听  在里面可以绑定很多个监听的方案
    
    },
  
    effects: {//类似于vuex的actions  进行异步触发的  effects的函数必须是一个Generator函数
        *LINK(){
            console.log("我被触发了");
        }
    },
  
    reducers: {//  修改上面的state的地方
     
    },
  
  };
  

3.设置请求

在dva中默认已经帮助我们封装好了异步请求 我们不需要单独去配置与下载 在service文件夹下新建文件 创建请求

import request from '../utils/request';

export function query() {
  return request('http://localhost:8888/userlist/getlink');
}

4.把写好的请求在effects中使用

// 1.引用我们刚才写好的请求
import {query} from "../services/userlink.js"
export default {

    namespace: 'userm',
  
    state: {
        name:"我是user模块中的数据"
    },
  
    subscriptions: {
    
    },
  
    effects: {
        // 2.使用异步请求
        // call 调用异步操作 并且得到成功的值
        *LINK({payload},{put,call}){
            console.log("我被触发了");
            // 下一步 使用call来获取成功的返回值
          let data= yield call(query
        }
    },
  
    reducers: {
     
    },
  
  };
  

5.把请求成功的数据复制给state方便组件使用( 先要交给reducers )

// 1.引用我们刚才写好的请求
import {query} from "../services/userlink.js"
export default {

    namespace: 'userm',
  
    state: {
        name:"我是user模块中的数据"
    },
  
    subscriptions: {
    
    },
  
    effects: {
        // 2.使用异步请求
        // call 调用异步操作 并且得到成功的值
        // put把effects中触发reducers
        *LINK({payload},{put,call}){
            console.log("我被触发了");
            // 下一步 使用call来获取成功的返回值
          let data= yield call(query

        //   把请求来的数据通过reducers修改给state
        // 下一步  通过put触发reducers
            yield put({type:"UP_NAME",data})

        }
    },
  
    reducers: {
     
    },
  
  };
  

6编写对应的reduicers

// 1.引用我们刚才写好的请求
import {query} from "../services/userlink.js"
export default {

    namespace: 'userm',
  
    state: {
        name:"我是user模块中的数据"
    },
  
    subscriptions: {
    
    },
  
    effects: {
        // 2.使用异步请求
        // call 调用异步操作 并且得到成功的值
        // put把effects中触发reducers
        *LINK({payload},{put,call}){
            console.log("我被触发了");
            // 下一步 使用call来获取成功的返回值
          let data= yield call(query

        //   把请求来的数据通过reducers修改给state
        // 下一步  通过put触发reducers
            yield put({type:"UP_NAME",data})

        }
    },
  
    reducers: {
        // 设置修改
        UP_NAME(state,payload){
            return {...state,name:payload.data}
        }
    
    },
  
  };
  

7 在页面读取数据

import Topbar from "../components/topbar.jsx"
import {connect} from "dva"
 function User(props) {
    let fun=()=>{
        // 触发effects进行异步操作
        // props.dispatch({type:"你要调用的模块命名空间名字/effect的名字"})
        props.dispatch({type:"userm/LINK"})    
    }
  return (
    <div>
        <Topbar></Topbar>
        user---{props.state.userm.name}
        
       <button onClick={fun}>点我触发异步操作</button> 
    </div>
  )
}
export default connect(state=>({state}))(User)

8 在运行之后出现了跨域问题 添加跨域 项目的根路径下有一个webpackrc的文件中添加跨域

{
    "proxy":{
        "/api":{
             "target":"http://localhost:8888",
             "changeOrigin":true,
             "pathRewrite":{
               "^/api":"/"
             }
        }
    }
}

9修改请求地址为/api

import request from '../utils/request';

export function query() {
  return request('/api/userlist/getlink');
}

10修改下请求的传递数据

// 1.引用我们刚才写好的请求
import {query} from "../services/userlink.js"
export default {

    namespace: 'userm',
  
    state: {
        name:"我是user模块中的数据"
    },
  
    subscriptions: {
    
    },
  
    effects: {
        // 2.使用异步请求
        // call 调用异步操作 并且得到成功的值
        // put把effects中触发reducers
        *LINK({payload},{put,call}){
            console.log("我被触发了");
            // 下一步 使用call来获取成功的返回值
          let data= yield call(query)
          console.log(data.data.msg);

        //   把请求来的数据通过reducers修改给state
        // 下一步  通过put触发reducers
            yield put({type:"UP_NAME",data:data.data.msg})

        }
    },
  
    reducers: {
        // 设置修改
        UP_NAME(state,payload){
            return {...state,name:payload.data}
        }
    },
  
  };
  

subscriptions 设置一个监听

可以在里面设置一些监听函数 必须页面修改了 鼠标移动了等等等

16 性能优化

完成如下效果

代码如下

在子组件的render中添加一个输出 在运行的时候发现每次运行的时候子组件都被调用了多次

那么大家发现当前我们写的有严重的性能问题;

原因是因为 我们在更新数据的时候使用setState修改整个数据 那么数据变了 便利的时候所有内容都要被重新渲染。

数量少没有关系 如果便利出来的数据很多 那么就会严重影响我们的性能

解决方式

使用生命周期的: shouldComponentUpdate(nextProps最新的props,nextState最新的state) 判定组件是否要更新

解决方式2 纯组件(PureComponent)--类组件中使用

PureComponent是优化react应用程序最重要的方法,组件发生更新时,组件的props和state没有改变,render方法就不会触发 .可以减少不必要的render次数,提高性能。

17 HOOK

react HOOK 是react16.8新增的一个特性 主要作用 就是让无状态组件/函数组件 可以使用状态 ref等一些特性

HOOK 不能在 class组件中使用

类组件与函数组件的区别

无状态组件 的创建比类组件的可读性更好 就是大大减少了组件编写代码量 在组件内容只有一个jsx编写效率更高

函数组件中不用this 因为函数组件没有实例化的过程

useState

useState 是reactHOOK给我们提供的 最基本最常用的一个HOOK 主要作用就是用来管理当前本地的状态

useState() 返回值是一个数组(长度为2)数组的第一项标识的是当前的值 数组的第二项标识的时候修改这个值的函数

let [xiaoming , setXiaoming]=useState(初始值)

创建与读取

import {useState} from "react"
let Funcom=()=>{
    // 使用useState()c创建函数组件的状态
    let [xiaoming,setxiaoming]=useState("你好么么哒!!!")
    return (
        <div>
            {/* 读取 */}
            <h1>我是一个函数组件--{xiaoming}</h1>
        </div>
    )
}
export default Funcom

修改

import {useState} from "react"
let Funcom=()=>{
    // 使用useState()创建函数组件的状态
    let [xiaoming,setxiaoming]=useState("你好么么哒!!!")
    return (
        <div>
            {/* 读取 */}
            <h1>我是一个函数组件--{xiaoming}</h1>
            {/* 修改数据 */}
            <button onClick={()=>{setxiaoming(xiaoming="你好呵呵哒")}}>点我修改</button>
        </div>
    )
}
export default Funcom

创建多个状态呢

1.你写多个useState

import {useState} from "react"
let Funcom=()=>{
    // 1.你写多个useState了解
    let [xiaoming,setxiaoming]=useState("1")
    let [xiaohong,setxiaohong]=useState("2")
  
    return (
        <div>
          {xiaoming}---{xiaohong}
        </div>
    )
}
export default Funcom

2.一次行创建多个值

import {useState} from "react"
let Funcom=()=>{
    // 1.你写多个useState了解
    // let [xiaoming,setxiaoming]=useState("1")
    // let [xiaohong,setxiaohong]=useState("2")
  
    // 2.一次性创建多个值
        let [xiaoming,setxiaoming]=useState({
            dataa:"第一个值1",
            datab:"第一个值2",
            datac:"第一个值3",
            datad:"第一个值4",
            datae:"第一个值5",
            dataf:"第一个值6"
        })
    return (
        <div>
          {/* {xiaoming}---{xiaohong} */}

          {xiaoming.dataa}----{xiaoming.datad}
        </div>
    )
}
export default Funcom

一次性创建多个值怎么修改

1.多个useState 要修改的话就依次调用其中的修改方法

2.一次行创建多个值的方式如何修改呢

import {useState} from "react"
let Funcom=()=>{
    // 1.你写多个useState了解
    // let [xiaoming,setxiaoming]=useState("1")
    // let [xiaohong,setxiaohong]=useState("2")
  
    // 2.一次性创建多个值
        let [xiaoming,setxiaoming]=useState({
            dataa:"第一个值1",
            datab:"第一个值2",
            datac:"第一个值3",
            datad:"第一个值4",
            datae:"第一个值5",
            dataf:"第一个值6"
        })

       let fun=()=>{
        //    一次性创建多个的修改操作   不要忘了保留原始数据
        setxiaoming({...xiaoming,datad:"我被改了"})
       }

    return (
        <div>
          {/* {xiaoming}---{xiaohong} */}

          {xiaoming.dataa}----{xiaoming.datad}
          <button onClick={fun}>点我修改</button>
        </div>
    )
}
export default Funcom

useRef

就是可以让函数组件使用ref的一个技术

import {useRef} from "react"
let Funcom=()=>{
//    1.创建出useRef
    let xiaoming=useRef(null)

    let fun=()=>{
        // 3.获取
        console.log(xiaoming.current.value);
    }
    return (
        <div>
            {/* 2.绑定使用 */}
          <input type="text" ref={xiaoming}/>
          <button onClick={fun}>点我得到输入框的值</button>
        </div>
    )
}
export default Funcom

useEffect

Function Component 不存在生命周期,所以不要把 Class Component 的生命周期概念搬过来试图对号入座。

useEffect就可以让函数组件使用生命周期

语法:

useEffect(第一个参数是一个函数,第二个参数是一个数组)

就是如下三个钩子函数的综合体

// 渲染完毕

componentDidMount() {

  

}

// 修改完毕

componentDidUpdate() {

  

}

// 准备销毁

componentWillUnmount() {

  

}

模拟componentDidMount

useEffect(()=>{console.log('第一次渲染时调用')},[])

第二个参数为一个空数组,可以模拟componentDidMount

因为函数组件每次更新的时候就是组件会被全部调用一次 传递了空数组就相当于欺骗了react我要监听某个内容 所以只是第一次调用

import {useEffect} from "react"
let Funcom=()=>{
    useEffect(()=>{
        document.title="么么哒"
        console.log("我自动执行了");
    },[])
    return (
        <div>
           <h1>函数组件可以使用生命周期</h1>
        </div>
    )
}
export default Funcom

模拟componentDidUpdate

没有第二个参数代表监听所有的属性更新但是注意首次也会触发

因为函数组件每次更新的时候就是组件会被全部调用一次 什么都不传那么每次修改就会触发

useEffect(()=>{console.log('任意属性该改变')}) 
import React, { useState, useEffect } from 'react'

export default function Demo() {
    let [xiaoming, setXiaoming] = useState("666")
    useEffect(() => {
        console.log("谁修改我都会触发")
    })
    let fun = () => {
        setXiaoming("我变了")
    }
    return (
        <>
            <div>demo--{xiaoming}</div>
            <button onClick={fun}>点我修改</button>
        </>
    )
}

监听多个属性的变化需要将属性作为数组传入第二个参数。

因为函数组件每次更新的时候就是组件会被全部调用一次 传递了值就会告诉react我要监听某个内容 所以只有这些值改变的时候就会被调用

useEffect(()=>{console.log('n变了')},[n,m]) 
import React, { useState, useEffect } from 'react'

export default function Demo() {
    let [xiaoming, setXiaoming] = useState("666")
    let [xiaohong, setXiaohong] = useState("777")
    // 因为监听了xiaohong 所以修改小明的时候不会触发
    useEffect(() => {
        console.log("谁修改我都会触发")
    },[xiaohong])
    let fun = () => {
        setXiaoming("我变了")
    }
    return (
        <>
            <div>demo--{xiaoming}--{xiaohong}</div>
            <button onClick={fun}>点我修改</button>
        </>
    )
}

模拟componentWillUnmount

通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源

useEffect函数返回的函数可以表示组件死亡

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

function Zi() {
    useEffect(()=>{
        // 调用子组件的时候执行一个定时函数
       let time= setInterval(()=>{
            console.log("你好")
        },1000)
        // 在effect中return 一个函数就是在销毁的时候执行
        return ()=>{
            console.log("我被销毁了")
            clearInterval(time)
        }
    })
    return (
      <div>我是Zi组件</div>
    )
  }

export default function Demo() {
    let [bool,setbool]=useState(true)
  return (
    <div>
        我是父组件
        <button onClick={()=>{setbool(!bool)}}>点我显示和隐藏子组件</button>
        {bool&& <Zi></Zi>}
       
        
    </div>
  )
}

useContext

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。

可以直接获取到context对象的Consumer消费者中的数据

在没有使用的时候跨组件传值

import React, { createContext } from 'react'
let context = createContext()
function Sun() {
    return (
        <div>
            孙
            <context.Consumer>
                {
                    (value)=>{
                        return (
                            <h1>{value.name}</h1>
                        )
                    }
                }
            </context.Consumer>
        </div>
    )
}

function Zi() {
    return (
        <div>
            子
            <Sun></Sun>
        </div>
    )
}

export default function Fu() {
    return (
        <context.Provider value={{name:"xixi",age:18}}>
            <div>
                父
                <Zi></Zi>
            </div>
        </context.Provider>
    )
}

使用useContext

import React, { createContext,useContext } from 'react'
let context = createContext()
function Sun() {
    // 使用useContext可以直接得到Consumer中的数据
    let value=useContext(context)
    return (
        <div>
            孙
            <h1>{value.name}</h1>
        </div>
    )
}

function Zi() {
    return (
        <div>
            子
            <Sun></Sun>
        </div>
    )
}

export default function Fu() {
    return (
        <context.Provider value={{name:"xixi",age:18}}>
            <div>
                父
                <Zi></Zi>
            </div>
        </context.Provider>
    )
}

useCallback

思考下面的代码

我们直到当修改数据的时候这个函数是会被重新调用的 那么为什么每次++的时候会从上一次的结果加1

而不是从原始数据1开始计算呢?

所以可以理解为useState是一个记忆函数可以记住上次的状态

import { useState } from "react"
let Funcom = () => {
    let [xiaoming, setxiaoming] = useState(1)
    return (
        <div>
            <h1>{xiaoming}</h1>
            <button onClick={() => { setxiaoming(++xiaoming) }}>点我修改</button>
        </div>
    )
}
export default Funcom

useReducer

就是在react中 函数组件如果对一个state有多次修改 那么可以使用useReducer来对其内容进行简化

语法:

let [保存这个值得变量,是对这个变量修改的一个方法]=useReducer(修改数据的reducer方法,"初始化值")

import {useReducer} from "react"
let Funcom=()=>{
    // 因为useReducer是对一个数据需要有多次修改的时候使用的
    // state就是当前的这个状态
    // dispat触发修改操作
   
    let [state,dispatch]=useReducer(reducer,18)
    function reducer(state,action){
        switch (action.type) {
            case "ADD":
                return state+1
                break;
            case "DEL":
                return state-1
                break;
        
            default:
                return state
                break;
        }
    }
    var add=()=>{
        dispatch({type:"ADD"})
    }
    var del=()=>{
        dispatch({type:"DEL"})
    }
  
    return (
        <div>

            <h1>useReducer--{state}</h1>
            <button onClick={add}>+1</button>
            <button onClick={del}>-1</button>
        </div>
    )
}

export default Funcom

或者
import {useReducer} from "react"
let Funcom=()=>{
    // 因为useReducer是对一个数据需要有多次修改的时候使用的
    // state就是当前的这个状态
    // dispat触发修改操作
    var reducer=(state,action)=>{
        switch (action.type) {
            case "ADD":
                return state+1
                break;
            case "DEL":
                return state-1
                break;
        
            default:
                return state
                break;
        }
    }
    let [state,dispatch]=useReducer(reducer,18)
    
    var add=()=>{
        dispatch({type:"ADD"})
    }
    var del=()=>{
        dispatch({type:"DEL"})
    }
  
    return (
        <div>

            <h1>useReducer--{state}</h1>
            <button onClick={add}>+1</button>
            <button onClick={del}>-1</button>
        </div>
    )
}

export default Funcom

18 React与TS

创建项目 create-react-app 项目名 --template typescript

扩展 npx

创建项目:npx create-react-app 项目名 --template typescript

在 npm version >= 5.2.0 开始,自动安装了npx。

npx 这条命令会临时安装所依赖的环境。命令完成后原有临时下载的会删掉,不会出现在 全局中。下次再执行,还是会重新临时安装。不用全局安装,不用担心长期的污染。

也就是说 npx 会自动查找当前依赖包中的可执行文件,如果找不到,就会去 PATH 里找。如果依然找不到,就会帮你安装

组件

创建一个tsx后缀名的文件

在需要的位置引用使用 但是引用的时候可能会出错

.

在引用时候不要加后缀名

数据传递

使用传统的方式进行数据传递的时候发现会报错

在进行数据传递的时候要使用 interface接口对类型限制 在子组件中进行限制设定

状态

直接定义状态会出现问题

需要使用接口

逆向传值

React 的核心概念之一是组件。在这里,我们将引用 React v16.8 以后的标准组件,这意味着使用 Hook 而不是类的组件。

组件的创建

函数组件---无状态组件 可读性非常好 减少了大量的冗余代码 因为在函数组件中 只有一个jsx 简化了创建组件的便捷性

函数组件不会被实例化 渲染性能就大大提升了

函数组件中不用使用this

函数组件代码精简不用实例化 可以尽可能少的节省创建时候的内存消耗 提高性能

函数组件在默认情况下不能使用 状态 不能使用ref 不能使用生命周期

FC就是FunctionComponent的缩写ts+react编写组件的语法

// 后面的<>是范型 中间的{}是没有props state占位的
export let Home:React.FC<{}>=()=>{
    return (
        <div>我事一个函数组件</div>asd
    )
}

props的使用

sinterface IUser{
    title?:String
}

export let Home:React.FC<IUser>=(props)=>{
        return (
            <div>我事一个函数组件---{props.title}</div>
        )
    }

父组件正常传递

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值