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属性:
-
state
是组件对象最重要的属性,值是对象(可以包含多个key-value
的组合) -
组件被称为“状态机”(状态机制),通过更新组件的
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 的对照表
功能 | npm | yarn |
---|---|---|
初始化 | npm init | yarn init |
安装依赖 | npm install | yarn install或者 yarn |
新增依赖 | npm install --save xxx | yarn add xxx |
全局安装 | npm install -g xxx | yarn global add xxx |
同时下载多个 | npm install --save xx1 xx2 | yarn add xx1 xx2 |
删除依赖 | npm uninstall --save xxx | yarn 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请求会有很多 而且我们需要区分 所以我们可以把数据请求拆分出来 单独管理这样一来 就可以增强项目的可维护性与可扩展性
大体分为如下内容
-
api 请求集中式管理
-
实现请求拦截
-
实现响应拦截
-
常见错误信息处理
-
请求头设置
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 首先是一个基于 redux 和 redux-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> ) }
父组件正常传递