React是用于构建用户界面的JavaScript库, 只关注于视图,主要表现为将数据渲染为HTML视图。
一、React 基础学习
npx普通安装
创建一个项目
npx create-react-app react-app 创建一个react-app项目
yarn start 启动项目(默认3000端口)
http://localhost:3000/
高级安装
--使用脚手架
npm i -g create-react-app
create-react-app xxx 创建新的项目
yarn start 启动项目(默认3000端口)
注意:React创建项目,不能大写字母开头。 它不像Laravel 一样,项目名称可以自定义。
项目名称使用 连字符号 - , 而不是使用 驼峰命名法
1.react与原生JS
前端开发流程
-
发送请求获取数据
-
处理数据
-
操作DOM呈现页面 React 只负责第3步
使用原生js的缺点:
1.原生JavaScript操作DOM繁琐、效率低(DOM-API操作UI)。
2.使用JavaScript直接操作DOM,浏览器会进行大量的重绘重排。
3.原生JavaScript没有组件化编码方案,代码复用率低。
React的特点:
1.使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互。
2.采用组件化模式、声明式编码,提高开发效率及组件复用率。
3. 在React Native中可以使用React语法进行移动端开发。
React的原理:
- React先根据数据创建虚拟Dom,再把虚拟Dom变为真实的Dom
- 当有数据增加时,先对比虚拟Dom是否有改变。没有改变的直接复用,有改变的进行调整。
- 比如渲染了100条,又增加了1条,对比前100条的虚拟Dom没有变化,直接复用,只需要重新渲染新增加的一条即可
2.diffing算法
diffing算法中,最关键的是Key值
1.虚拟DOM中key的作用:
-
简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
-
详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DON],
随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:-
旧虚拟DOM中找到了〔新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变,直接使用之前的真实DOM
(2).若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实 -
旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到到页面
-
2.用index作为列表的key可能会引发的问题:
-
若对 数据进行:逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新==>界面效果没问题,但效率低。
-
如果结构中还包含输入类的DOM, 会产生错误DOM更新==>界面有问题。
-
如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表数据,用index是没问题的
-
使用唯一字段id和index的区别
-
更新后数据虚拟DOM: <div key=3>小张--- 18</ div> (转成真实DOM) <div key=1>小李--- 18</ div> (不做更改) <div key=2>小王--- 19</div> (不做更改)
-
更新后数据虚拟DOM: <div key=0>小张—--18</ div> (转成真实DOM) <div key=1>小李---18</ div> (转成真实DOM) <div key=2>小王--- 19</div> (转成真实DOM)
-
3.开发中选择key
- 最好使用每条数据的唯一标识作为key,如果id,手机 号,等等
- 如果后端的接口没有返回对应的标识字段,可以使用nanoid 模块,获取随机的唯一id值
4.关于虚拟DOM:
-
本质是0bject类型的对象(一般对象),虚拟DOM是一个对象,而真实DOM是一个DOM节点
-
虚拟DOM比较’轻’,真实DOM比较’重",因为虚拟DOM是React内部使用的,无需真实DOM上那么多属性
-
虚拟DOM最终会被React转化为真实D0M,呈现在页面
3.React事件处理
组件维护状态,状态存储数据,数据驱动页面
通过状态改变数据,以数据驱动页面的更新 ,react会根据数据的变化 自动更新页面
-
通过onXxx属性指定事件处理函数(注意大小写)
(1. React使用的是自定义(合成)事件,而不是使用的原生DOM事件 — 为了更好的兼容性
(2. React中的事件是通过"事件委托方式" 处理的(委托给组件最外层的div元素 — 为了更高效
-
通过event.target得到发生事件的DOM元素对象
// 函数嵌套函数就会形成闭包。 所以说需要去改变this指向
// 将inputChange里面的this指向当前的this. 否则形成闭包的函数中不能获取this.
<input type="text" value={this.state.inputValue} onChange={this.inputChange.bind(this)}/>
// 通过bind绑定当前的this
// 这个事件是由input标签触发的,所以它的this指向input对象
// 这会触发一个事件,默认是target, 数据是e.target.value
inputChange(e){
this.setState({ // 改变状态, 赋值无效
inputValue:e.target.value
})
}
// 通过箭头函数实现this的上级查找
// this会从上一级中找
inputChange = ()=>{
this.setState({ // 改变状态, 赋值无效
inputValue:e.target.value
})
}
4.React模板语法
1.基本语法
-
jsx语法,不要写引号,类似XML文件的格式,返回的是标签。
-
对象格式的内联样式:多个节点,使用变量{title},使用内联样式style={ {color:‘red’, fontSize:‘50px’} }
-
类名变成className :而不是class
-
样式的驼峰习惯:font-size 改成fontSize, 最外面的{}表示的是里面数据是js语法,类似于一个json对象的格式""
-
内联事件的驼峰习惯:把原来的onclick改成 onClick, onClick = {demo}, 注意:有括号表示自动运行函数,而一般采用无括号或者回调函数的格式绑定事件。
2.运行实例
1.使用react的模板语法
<div id="test">
</div>
<!--引入react核心库-->
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<!--引入react-dom库,用于支持react操作Dom-->
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<!--引入babel,用于将jsx转为js-->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!--引入babel,用于将jsx转为js-->
<!--一定要写text/babel -->
<script type="text/babel">
let id = "title";
let content = "Hello";
// 1。创建虚拟DOM
const VDOM = <h1>Hello</h1>; // 1.单节点
const VDOM = <h1 id="title"><span>Hello</span></h1>; // 2.多个节点
const VDOM = (
<h1 id="title"> // 3.多个节点,缩进的格式
<span>Hello</span>
</h1>
);
const VDOM = (
<h1 id={title} className="myTitle">
<span style={ {color:'red', fontSize:'50px'} }>{Hello}</span>
</h1>
);
// 2.渲染虚拟DOM到页面
ReactDOM.render( VDOM,document.getElementById( 'test'))
</script>
2.两种创建虚拟DOM的方式(jsx , js 两种方式)
<!--准备一个窗口-->
<div id="test">
</div>
<!--引入react核心库-->
<script src="./js/react.development.js"></script>
<!--引入react-dom库,用于支持react操作Dom-->
<script src="./js/react-dom.development.js"></script>
<script >
// 1。创建虚拟DOM
const VDOM = React.createElement('h1',{id:'title'},'Hello');
// 再添加子节点
const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello'));
// 2.渲染虚拟DOM到页面
ReactDOM.render( VDOM,document.getElementById( 'test'))
</script>
5.jsx 与模块化
(1. 什么是jsx?
1.基本概念
-
全称:JavaScript XML
-
react定义的一种类似于XML的JS扩展语法: js+XML本质是React.createElement (component,props …children)方法的语法糖
-
作用:用来简化创建虚拟DOM
(1.写法:
var ele = <h1>Hello Jsx!</h1>
(2.注意1:它不是字符串,也不是HTML/XML标签
(3.注意2∶它最终产生的就是一个js对象
-
基本语法规则
(1.遇到<开头的代码,以标签的语法解析: html同名标签转换为html同名元素,其它标签需要特别解析(2.遇到以{开头的代码,以js语法解析:标签中的js表达式必须用{ }包含,即引入变量的时候
-
babel.js的作用
(1. 浏览器不能直接解析JSx代码,需要babel转译为纯s的代码才能运行(2. 只要用了jsx,都要加上type=“text/babel”,声明需要babel来处理
2.什么是XML
(1.XML早期用于存储和传输数据
<student>
<name>LIli></name>
<age>17</age>
</student>
(2. JSON是目前更流行的的存储和传输数据的方式
(1.JSON.stringify可以将json格式 转换成字符串, 串行化
(2.JSON.parse 可以将字符串转换成原来的json对象,反串行化
3.jsx语法规则
1.定义虚拟DOM时,不要写引号
2.标签中混入JS表达式时,要用{}
3.样式的类名不要用class属性,要使用className属性
4.内联样式要用style={{key: value}}的形式去写
5.只能有一个根标签,多个标签会出错
6.标签必须闭合,单标签可以使用/来闭合
7.标签首字母
a.如果标签首字母是小写,会把该标签转为html中同名元素,如果找不到同名的元素,就会报错
b.如果标签首字母是大写,react就去渲染对应的组件,如果组件没有定义,就会报错
4.实例小案例
一定要注意区分:js语句 和 js表达式
表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
语句:if,for,switch逻辑结构
1.表达式
a + b
demo(1)
arr.map()
function a (){ }
三目运行符
2.语句
for(){ }
if(){ }
switch(){ }
3.js中常用map, 和 三目运算符号 返回数据
(2.组件与模块学习
1. 模块
1.理解:向外提供特定功能的js程序,一般就是一个js文件
2.为什么要拆成模块︰随着业务逻辑增加,代码越来越多且复杂。
3.作用:复用js,简化js的编写,提高js运行效率
-
组件
1.理解:用来实现局部功能效果的代码和资源的集合(html/cssjs/image等等)
2.为什么要用组件:一个界面的功能更复杂
3.作用:复用编码,简化项目编码,提高运行效率 -
模块化
当应用的js都以模块来编写的,这个应用就是一个模块化的应用 -
组件化
当应用是以多组件的方式实现,这个应用就是一个组件化的应用
6.类式与函数式组件
(1.函数式组件
<script type="text/babel">
//1.创建函数式组件
function Demo( ) {
console.log(this)// this是 undefined,因为 babel在翻译的时候,开启了严格模式
return <h2>我是用函数定义的组件,适用于简单组件的定义</h2>
}
// 2、渲染组件到页面
ReactDOM.render(<Demo />, document. getElementById( 'test ' ))
/**
执行了ReactDOM.render (<MyComponent />,...之后,发生了什么
1. React解析组件标签,找到MyComponent这个组件
2,发现组件是使用函数定义的,随后调用该函数,将函数返回的虚拟DOM转为真实的DON,随后呈现在页面中
**/
</script>
(2.类式组件
(1.类的基本知识
- 类中的构造方注不是必须写的,要对实例进行一些初始化的操作才写
- 如果A类继承B类,且A类中写了构造器,A类构造器必须写super()方法
- 类中定义的方法,都是放在了类的原型对象上,供实例去使用
- 在继承中,如果子类使用的方法没有在在子类原型对象上找到,就会通过原型链继续向父级查找
class Person {
//构造方法
constructor(name, ad) {
// 没有对应的字段,就自动创建属性,并赋值
this.name = name
this.age = age
}
//类中可以直接写赋值赋值语句(不用关键字),
//下面代码的含义是:给Car的实例对象添加一个属性,名为wheel,值是4
wheel= 4
//一般方法
speak(){
// speak方法在类的原型对象上,供实例调用
//通过Person实例调用speak时,speak中的this就是Person实例
console.log(`我的名字是:${this.name},我的年龄是;${this.age}`)
}
}
//创建一个Student类,继承Person类
class Student extends Person {
constructor(name,age, grade) {
// super() //子类重写父类的构造方法,一定要使用super()
// this.name = name
// this.age = age
super(name,age);
this.grade = grade
}
//重写父类的speak方法
speak() {
console.log(`我的名字是:${this.name},我的年龄是:${this.age},我的成绩是:${this.grade}`);
}
}
let student = new Student( 'Bob ",20);
student.speak()
(2.类式组件的使用
//创建一个Person类
<script type="text/babel">
//1,创健类式组件
class MyComponent extends React.Component{
render() {
//render 放在MyComponent类的原型对象上,供实例调用
//render中的this是MyComponent的实例对象
console.log( render中的ttis是; ',this)// MyComponent {}
return <h2>我是用类定义的组件,适用于复杂组件的定义</h2> //state
}
// 2,将组件渲染到页面
ReactDOM.render(<MyComponent />, document.getElementById( 'test ' ))
/**
执行了ReactDOM.render ( <MyComponent />,..、之后,发生了什么
1.React解析组件标签,找到MyComponent这个组件
2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用原型上的render方法,
将render方法返回的的虚拟DOM转为真实的DOM,随后星现在页面中
**/
// 组件通过状态来驱动数据的更新,将虚拟DOM转换成真实DOM ,渲染成页面
</script>
(3.类中的this指向
(1.不同的this
组件的状态驱动页面的改变
//speak方法在类的原型对象上,供实例调用
//通过Person实例调用speak时,speak中的this就是Person实例
let p = new Person('Tom'.,18);
p.speak();
//把实例中的方法同值给一个变量
// 通过变量调用的方法,this 是undefined
// 其实,在类中,方法会默认开始局部的严格模式,所以this是undefined
let x = p.speak; x();
// 在script标签中,调用全局的函数,this就指向window对象
function demo(){
console.log(this);
}
demo();
(2.改变this指向
let x = demo.bind({a:1,b:2})
x() ; 将demo函数中的this指向了新的对象, demo想要绑定新对象,demo就要绑定哪个对象的this
(3.//使用赋值语句,可以将方法添加到实例本身,而不是类的原型上面
//自定义的方法--要用赋值语句+箭头函数,箭头函数没有this,会向上去同级寻找this
changeWeather =()=> {
let isHot = this.state.isHot
this.setState({isHot,!isHot})
}
(4.类式组件与函数式组件的区别
-
类式组件需要继承
React.Component组件
,并且需要把返回的页面数据放在 **render(){}
**方法中 -
类式组件则不需要以上操作, 但它们都需要使用
return (标签)
返回页面数据
7.ref 属性绑定标签
(1.字符串形式的ref
注意: 因为这种方式会存在问题,也一直没有解决这个问题,所以在未来的版本可能会移除这种绑定方法。值得一提的是,vue 中就采用这种字符串的方式进行绑定的。
showData = ()=>{
let {input1} = this.refs; 把refs的数据解构出来
let {input1:value} = this.refs; 把refs的数据解构出来
let input1 = this.refs.input1; 把refs的数据解构出来
alert(input1.value);
};
<input ref="input1" type="text" placeholder="点击按钮显示数据"/>
(2.回调函数的ref
什么是回调函数?
你定义的函数, 你没有调用, 函数最终执行了
// 1.内联回调函数
showData = ()=>{
let {input1} = this; // 数据从this中获取
let input1 = this.input1; // 数据从this中获取
alert(input1.value);
};
<input ref={c=> this.input1 = c} type="text" placeholder="点击按钮显示数据"/>
// 2.绑定类内定义的函数作为回调函数
saveinput = (c)=>{
this.input1 = c;
};
<input ref={this.saveinput} type="text" placeholder="点击按钮显示数据"/>
1.两种方式的区别
-
绑定类内定义的函数的方式, 只是不会删除回调函数实例,
-
而内联函数的方式每次都会在渲染时创建一个新的函数实例。
2.回调ref具体运行了几次。
- 如果ref回调函数是以”内联函数的方式“定义的,在更新过程中它会被执行两次,第一次传入参数null,然后第二次会传入参数DOM元素。这是因为在每次渲染时会创建一个新的函数实例,所以React清空旧的ref并且设置新的。
- 通过将ref 的回调函数定义成”绑定类内定义函数的方式“可以避免上述问题。加载的时候可能会更缓慢一点,但是大多数情况下它是无关紧要的。
(3.hooks的createRef
注意: this.myRef.current , e.target. 和 ref绑定的 this.input 都是对应的DOM标签
import React,{useState,useEffect,useRef} from 'react';
myRef = React.createRef();
showData = ()=>{
// 这个容器是专人专用,每个ref都要使用单独的一个
console.log(this.myRef.current.value);
};
<input ref={this.myRef} type="text" placeholder="点击按钮显示数据"/>
8.非受控组件与受控组件
(1.受控组件(事件)
(1.基本概念
onChange={this.saveUsername}
this.setState({password:event.target.value});
- 随着你的输入维护状态,表单数据由状态维护state.
- 推荐使用受控组件,不用ref,因为官方不让过度使用ref
- 采用事件触发的方式 , 使用类内定义的函数,并采用state维护数据。
class Login extends React.Component{
// 初始化数据
state = {
username:"",
password:"",
};
handleSubmit = (event)=>{
event.preventDefault(); // 阻止默认行为
let {username,password} = this.state; // 从state中获取数据
alert(username+password);
};
// 保存输入的用户名
saveUsername = (event) =>{
this.setState({username:event.target.value});
};
// 保存输入的密码
savePassword = (event) =>{
this.setState({password:event.target.value});
};
render(){
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text" /><br />
密码:<input onChange={this.savePassword} type="password" /><br />
<button>登录</ button>
</form>
)
}
}
(2.非受控组件(ref)
ref={event=>this.input=event}
- 表单数据将交由DOM节点处理。表单的数据现用现取,从ref绑定DOM节点中获取表单数据
- 采用回调函数的ref, 并且是内联回调函数式Ref, 让数据更语义化,也代替了e.target
- 数据不受到表单原来事件的影响,,直接获取绑定的DOM节点数据
<input type="text" id="focousInput" ref={(event)=>{this.input=event}} />
this.setState({
// inputValue:e.target.value // 原来的事件处理方式
inputValue:this.input.value // 采用非受控组件,通过回调函数的Ref实现
})
(2.常用实例
// 一般用于获取绑定对象的DOM的子类
<ul ref={(event)=>{this.ul=event}}>
...
</ul>
通过Ref绑定的标签 this.ul是 对应的标签
console.log(this.ul.querySelectorAll("li"))
(3.使用实例
class Login extends React.Component{
handleSubmit = (event)=>{
event.preventDefault(); // 阻止默认行为
let {username,password} = this;
alert(username.value+password.value);
};
render(){
return (
<form onSubmit={this.handleSubmit}>
用户名:<input ref={c =>this.username = c} type="text" /><br />
密码:<input ref={c =>this.password = c} type="password" /><br />
<button>登录</ button>
</form>
)
}
}
(3.高阶函数和函数柯里化
(1.高阶函数
如果一个函数满足下面两个条件之一,就是高阶函数
-
返回值是一个函数
-
接收的参数是一个函数
-
常见的高阶函数:Promise setTimeout arr.map()
// 主动触发函数,以获取一个回调函数
saveFormData = (dataType) =>{
return (event) =>{
this.setState({[dataType]:event.target.value})
} // 通过【】让 dataType变成key
};
render(){
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveFormData("username")} type="text" /><br />
密码:<input onChange={this.saveFormData("password")} type="password" /><br />
<button>登录</ button>
</form>
)
}
(2.函数的柯里化
- 通过函数调用持续返回函数的方式,实现多次接收参数,最后统一处理的函数编码方式
// 执行多次返回的回调函数
function sum(a){
return (b) =>{
return (c) =>{
return a+" "+ b + " " +c;
}
}
}
console.log(sum(1)(2)(3))
(3.不使用函数柯里化(受控组件)
saveFormData = (dataType,value) =>{
this.setState({[dataType]: value}) // 通过【】让 dataType变成key
};
render(){
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={ (event) => this.saveFormData("username",event.target.value)} type="text" /><br />
密码:<input onChange={ (event) => this.saveFormData("password",event.target.value)} type="password" /><br />
<button>登录</ button>
</form>
)
}
9.component生命周期
(1.生命周期
什么是组件的生命周期?
-
组件从创建到死亡它会经历一些特定的阶段。
-
React组件中包含一系列勾子函数(生命周期回调函数),会在特定的时刻调用。
componentWillMount 组件将要完成
componentDidMount (完成前都有render) 组件构建完成
componentWillReceiveProps(子组件) 组件将接收父组件的信息
shouldComponentUpdate 确认组件是否应该更新
componentWillUpdate 组件将要更新
componentDidUpdate 组件更新完成
componentWillUmmount 组件将要卸载
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4bfAFV17-1661413920649)(C:\Users\Icy-yun\AppData\Roaming\Typora\typora-user-images\image-20211214154916127.png)]
class Life extends React.Component {
// 构造器
constructor(props) {
console.log('count---constructor');
super(props);
this.state = {count:0};
}
add = ()=>{
// 获取原来的状态
let {count} = this.state;
this.setState({count:count+1})
};
// ------------------ 第一-----------------
// 1.组件将要挂载的勾子
componentWillMount(){
console.log('count---componentWillMount');
}
// 2.组件已经挂载的勾子
componentDidMount(){
console.log('count---componentDidMount');
}
// --------------------第二 -------------------
// 3.组件将会接收props的属性,
// 只有在组件已经存在dom中,才会执行,第一次加载组件的时候不会执行。
// componentWillReciveProps(){
console.log("child----componentWillReciveProps")
}
// 4.应该组件更新吗?
// 组件控制组件更新的”开关“ , 需要有一个返回值,确认是否更新
shouldComponentUpdate(){
console.log("Console. ---- showComponentUpdate");
return true; // 是否允许更新
}
// 5.组件将要更新
componentWillUpdate(){
console.log("Console. ---- componentWillUpdate");
}
// 6.组件更新完成的勾子
componentDidUpdate(){
console.log("Console. ---- componentDidUpdate");
}
-----强制更新------
// 强制更新按钮的回调
force = ()=>{
this.forceUpdate();
};
// -------------------第三 -----------------------
// 卸载组件按钮的回调
death = ()=>{
// 卸载组件所在的节点。
ReactDOM.unmountComponentAtNode(document.getElementById("test"))
};
// 7.组件将要制卸载的勾子
componentWillUnmount(){
console.log('count---componentWillUnmount');
}
render(){
console.log('count---render');
return (
<div>
<h3 >当前求和为:{this.state.count}</h3>
<button onClick = {this.add}>点我+1</button>
<button onClick = {this.death}>点击卸载</button>
</div>
)
}
}
子组件优化:在父子之前传递数据,不允许同时进行页面的刷新, 这会导致性能的卡慢。
只有在props数据改变时需要的时候才更新组件
// 子组件
shouldComponentUpdate( nextProps,nextState){
if(nextProps.content !==this.props.content){
return·true; // 只有在下一次传递的值不是当前的props的值, 就允许更新
}else{
return false;
}
}
(2.新生命周期
原生命周期:
componentWillMount 已经被修改 (需要添加UNSAFE_)
componentWillUpdate 已经被修改 (需要添加UNSAFE_)
componentWillReceiveProps 已经被修改 (需要添加UNSAFE_)
未来版本也可以被删除, render之前的 Will 将会被移除
getDerivedStateFromProps // 从props中获取派生的state状态 (新)
componentDidMount (完成前都有render) 组件构建完成
getSnapshotBeforeUpdate(preProps,preState) // 在更新之前获取一个快照, 返回的值可以被componentDidUpdate利用 (新)
shouldComponentUpdate 确认组件是否应该更新
getSnapshotBeforeUpdate() // 在更新之前获取新数据的高度
componentDidUpdate(prevProps, prevState, oldHeight) // 组件完成更新
componentWillUmmount 组件将要卸载
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iinWulEN-1661413920650)(C:\Users\Icy-yun\AppData\Roaming\Typora\typora-user-images\image-20211214160811156.png)]
// ------------------ 第一 -------------
// 从props中获取派生的state状态 (新)
static getDerivedStateFromProps(props,state){
console.log("count-------getDerivedStateFromProps",props,state);
// state.object
// null
return null; // 值会继续更新,
// return props // 值将不变
}
// 组件将要挂载完毕的勾子
componentDidMount(){
setInterval(()=>{
let {newsList} = this.state;
let news = '新闻' + (newsList.length + 1);
// 更新状态
this.setState({newsList:[news,...newsList]})
},1000)
}
// ------------------ 第二 -------------
// 在更新之前获取一个快照, 返回的值可以被componentDidUpdate利用 (新)
// 常用于滚动计算。
getSnapshotBeforeUpdate(preProps,preState){
console.log("count------getSnapshotBeforeUpdate",preProps,preState);
return 'hello';
}
// 控制组件更新的”开关“ , 需要有一个返回值,确认是否更新
shouldComponentUpdate(){
console.log("Console. ---- showComponentUpdate");
return true;
}
// 在更新之前获取数据的高度
getSnapshotBeforeUpdate(){
// 组件更新之前的高度
let oldHeight = this.refs.list.scrollHeight;
return oldHeight;
}
// 组件完成更新
componentDidUpdate(prevProps, prevState, oldHeight) {
// 组件更新之后的高度
let newHeight = this.refs.list.scrollHeight;
this.refs.list.scrollTop += newHeight - oldHeight;
}
// 组件将要制卸载的勾子
componentWillUnmount(){
console.log('count---componentWillUnmount');
}
(3.父组件渲染流程
一、初始化阶段:由ReactDOM.render()触发—初次渲染
1. constructor()
2. componentwillMount()
3. render()
- componentDidMount()
(一般在这个钩子中做一些初始化的事,例如:开启定时器,发送网络数据,订阅消息)
二、更新阶段:由组件内部this.setSate()或父组件render触发
-
shouldComponentUpdate()
-
componentwillupdate()
-
render()
-
componentDidUpdate() // 一般完成前的操作都是render渲染操作。
三、卸载组件:由ReactDOM.unmountComponentAtNode()触发
- componentwillUnmount()
(一般在这个钩子中做一些收尾的事,例如:关闭定时器,取消订阅消息)
class A extends React.Component {
// 初始化状态
state = {carName:'奥迪'};
//换车的回调
changeCar = ()=>{
this.setState({carName:'宝马'})
}
render(){
console.log('count---render');
return (
<div>
<h3 >我是A组件</h3>
<button onClick={this.changeCar}>换车</button>
<B carName ={this.state.carName} />
</div>
)
}
}
class B extends React.Component {
// 组件将要接收新的props的钩子---第一次接收props的时候不会执行,只有后来props更新了,才会触发
componentWillReceiveProps(props){
console.log('B----componentWillReceiveProps',props)
}
// 控制组件更新的”开关“ , 需要有一个返回值,确认是否更新
shouldComponentUpdate(){
console.log("B---- showComponentUpdate");
return true;
}
// 控制组件更新的”开关“
componentWillUpdate(){
console.log("B ---- componentWillUpdate");
}
// 组件更新完成的勾子
componentDidUpdate(){
console.log("B ---- componentDidUpdate");
}
render(){
console.log('B---render');
return (
<div>
<h3 >我是B组件</h3>
<p>我的车是:{this.props.carName}</p>
</div>
)
}
}
// 2.渲染组件到页面
ReactDOM.render(<A />,document.getElementById("test"))
10.PropTypes的数据校验
在prop-types中有PropTypes模块
import PropTypes from 'prop-types';
XiaojiejieItem.propTypes = {
avaname:PropTypes.string.isRequired, //必需传参
content:PropTypes.string, // 期望传参
index:PropTypes.number,
deleteItem:PropTypes.func
}
XiaojiejieItem.defaultProps = {
avaname:"阿美服务" // 默认值
}
// 这是对父类传递给子类的数据校验
(设置在子组件的内部。相当于静态变量)
类内是static ,而类外是 类名 + .
11.setState的使用
1.setState的基本语法
setState的两种写法对象式的setState:
- setState(statechange,[callback]) 对象式
- stateChange为状态改变对象(该对象可以体现出状态的更改)
- callback是可选的回调函数,它在状态更新完毕、界面也更新后(render调用后)才被调用函数式的setState:
- setState(updater,[callback]) 函数式
- updater为返回stateChange对象的函数。
- updater可以接收到state和props.
- callback是可选的回调函数,它在状态更新、界面也更新后(render调用后)才被调用。
- 总结:
- 对象式的setState是函数式的setState的简写方式(语法糖)
- 使用原则:
- 如果新状态不依赖于原状态===>使用对象方式
- 如果新状态依赖于原状态===>使用函数方式
- 如果需要在setState()执行后获取最新的状态数据,要在第二个callback函数中读取
注意:setState是异步的,所以需要 在回调结束后再打印结果
addList(){
this.setState({
list:[...this.state.list,this.state.inputValue] ,
inputValue:""
},()=>{
// 添加一个回调函数
console.log(this.ul.querySelectorAll("li").length)
})
}
// 获取原来的状态
// let {count} = this.state;
// let count = this.state.count;
// 更改状态
// 方式一: 对象形式
// this.setState({count:count+1},()=>{
// console.log(this.state.count)
// })
// 方式二: 函数方式
//this.setState(state=>({count:state.count+1}))
this.setState((state,props)=>{
console.log(state,props) // 可以接收到state和props
return {count:state.count+1}
})
2.state的基本概念
- 状态的处理
- 获取 isHost的值
let isHot = this.state.isHot; - 状态要通过setState更新,这是一种合并,而不是替换
- 获取 isHost的值
this.setState({isHot:!isHot})
-
总结state
- state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
- 组件被称为"状态机"通过更新组件的state来更新对应的页面显示数据(重新渲染组件),组件里面维护着状态,状态里面存着数据,在更新状态里面的数据时,组件就能重新渲染
-
注意点
-
组件中render方法中的this为组件实例对象
-
组件自定义的方法中this为undefined ,如何解决?
a.强制绑定this:通过函数对象的bind
b.赋值语句+箭头函数 -
状态数据,不能直接修改或更新
<script type="text/babel">
// 1。创建虚拟DOM
const my = "title";
class Weather extends React.Component{
constructor(props){
super(props);
this.state = {isHot:true,wind:'大风'};
this.changeWeather = this.changeWeather.bind(this);
// 将当前原型对象中的changeWeatherthis改变为实例的this对象。
// 前面的changeWeather可以改变,就是一个变量而已
}
// 页面的渲染,执行了1+n次
render(){
console.log(this);
let {isHot,wind} = this.state; // 将this.state中的数据解构出来
return <h1 id={my} onClick={this.changeWeather}>今天天气很{this.state.isHot ? "炎热" : "凉爽" }</h1>
}
// 绑定事件
changeWeather(){
//changeweather 方法在哪?changeweather方法在类的原型对象上,供Weather实例调用
// 通过wWeather实例调用changeweather时,changeweather中的this就是Weather实例
//类中的方法默认开始局部的严格模式,所以当类内部调用changeWeather方法的时候,this就是undefined
//console.log(this.state.isHot)
// 获取 isHost的值
let isHot = this.state.isHot;
// 状态要通过setState更新,这是一种合并,而不是替换
this.setState({isHot:!isHot})
// 注意:状态state不能直接修改,this.state.isHot = !isHot无效
}
//使用赋值语句,可以将方法添加到实例本身,而不是类的原型上面
//自定义的方法--要用赋值语句+箭头函数,箭头函数没有this,会向上去同级寻找this
changeWeather = ()=> {
let isHot = this.state.isHot;
this.setState({isHot:!isHot})
}
}
// 2.渲染虚拟DOM到页面
ReactDOM.render( <Weather />,document.getElementById( 'test'))
</script>
12.html标签解析
1.dangerouslySetInnerHTML解析
注意:为什么会是dangerously解析html标签呢?那是因为渲染html标签容易受到xss攻击,所以一般都不建议大量解析html字符串。
<li key={index+item} onClick={this.deleteItem.bind(this,index)}
dangerouslySetInnerHTML={{__html:item}}>
</li>
2.其它知识
1.label友好型标签,for里面填写input的id值,点击label的时候,就会解锁focus与datalist很类似
<label for="focousInput">增加服务</label>
<input type="text" id="focousInput" className="input" />
2.input标签中的checked 如果需要设置变量,则需要把checked改成 defaultChecked
3.vscode快捷键
Snippet | Renders |
---|---|
imr | Import React |
imrc | Import React / Component |
imrd | Import ReactDOM |
imrs | Import React / useState |
imrse | Import React / useState useEffect |
impt | Import PropTypes |
impc | Import React / PureComponent |
cc | Class Component |
ccc | Class Component With Constructor |
cpc | Class Pure Component |
fc | Function Component |
cdm | componentDidMount |
uef | useEffect Hook |
cwm | componentWillMount |
cwrp | componentWillReceiveProps |
gds | getDerivedStateFromProps |
scu | shouldComponentUpdate |
cwu | componentWillUpdate |
cdu | componentDidUpdate |
cwu | componentWillUpdate |
cdc | componentDidCatch |
gsbu | getSnapshotBeforeUpdate |
ss | setState |
ssf | Functional setState |
usf | Declare a new state variable using State Hook |
ren | render |
rprop | Render Prop |
hoc | Higher Order Component |
13.props的基本使用
(1.传递props属性, 标签属性
<li>姓名:{this.props.name}</li>
ReactDOM.render(<Person name="LILI" sex="女" age="18"/>,document.getElementById("test2"))
(2.批量传递…pops属性(展开运算符)
let p = {name:‘LILI’, sex:“女”,age:18}
// 在babel中,展开运算符展开对象,只能在标签组件通过属性传递props的时候使用
ReactDOM.render(<Person {…p}/>,document.getElementById(“test2”))
(3.限制props传递的属性, 使用propTypes
注意: props是只读的,不能修改它的内容
1.创建类式组件 2.渲染组件到页面
(4.简写形式, 如果需要把内容放在类里, 需要添加静态前缀static
(5.构造器中使用props
//基本用不到构造器—–了解一下就行
constructor( props){
//构造器中是否传入props,super中是否传入props,
//取决于:是否在构造器中通过this获取props
super(props);
console.log(this.props)
}
二、 React高级学习
1.westorm快捷键
rcc + tab 键 用es6模块系统创建一个React组件类
rccp + tab键 用es6模块系统创建一个React组件类,带有propTypes
rsf + tab键 以命名函数的形式,创建一个没有状态的React组件
2.组件化编码的过程
- 拆分组件,拆分界面,抽取组件
- 实现静态组件,实现静态效果
- 实现动态组件
- 动态 显示初始化数据
- 数据类型
- 数据名称
- 保存的位置
- 交互的事件
- 动态 显示初始化数据
1.引入样式与图片
(1.引入css样式
1.普通使用方法
在js文件定义css文件
直接在js文件中
import './login2.css';
使用HashRouter(不推荐)
import {HashRouter} from 'react-router-dom';
2.js引入模块css
css文件名使用xxx.module.css
在js中引入
import styles from './login.module.css';
标签中使用变量
<button className = {styles.button}>提交</button>
3.引入jsx 样式(不建议)
<style jsx>
{`
.HorizontalScrollTrackInner{
top:14px!important;
background:red;
height:8px;
}
.HorizontalScrollTrack::after{
height:12px!important;
background:#b8b8b8!important;
}
`}
</style>
(2.引入img图片
图片需要单独作为变量引入。
import Logo from '../../assets/images/user.jpg'
<img src={Logo} alt="" />
图片加速网站 : https://images.weserv.nl/?url= 如果有https://,则需要先去除
<img src={"https://images.weserv.nl/?url="+movieObj.img.replace('https://','')} alt={movieObj.title} width="300" height="300"/>
头部检测来源域(也可以实现图片展示)
在index.html头部添加
<!--禁止检查来源域-->
<meta name="referrer" content="never">
其它加速网站
以下是网络中收集的一些图片镜像缓存服务,持续更新。
1.https://img.noobzone.ru/getimg.php?url= (失效)
2.https://collect34.longsunhd.com/source/plugin/yzs1013_pldr/getimg.php?url=
3.https://ip.webmasterapi.com/api/imageproxy/ (失效)
4.https://images.weserv.nl/?url=(失效)
5.https://pic1.xuehuaimg.com/proxy/
6.https://search.pstatic.net/common?src= (失效)
(3.引入nanoid随机值模块
nanoid组件
uuid
yarn add nanoid 生成不重复的id
import {nanoid} from 'nanoid';
nanoid() 获取唯一的id值
(4.引入less 预编译模块
此方式很容易解决css样式污染的问题!所以建议使用less,或者 sass开发。
(1.安装模块
yarn add less less-loader@4.x.x
注意:一定要把版本降到5.0.0以下,否则会重复出现以下报错!
.....Less Loader has been initialized using an options object ...
(2.找到webpack.config.js
- react脚本架默认css 和 sass , 所以需要修改webpack配置,使其支持less
react-app-rewired
组件是继承了react-scripts模块的内容,所以webpack配置是在react-scripts包中。
react-scripts/config/webpack/webpack.config.js
(3.修改webapck.config.js配置
// 在style files regexes 下方添加匹配规则 , 支持less
const lessRegex = /\.less$/;
const lessModuleRegex = /\.module\.less$/;
// 向getStyleLoaders函数中注册Less
const getStyleLoaders = (cssOptions,lessOptions, preProcessor) => {
....
{
loader: require.resolve('less-loader'),
options: lessOptions,
},
....
}
// 搜索sassRegex, 仿写sass,在上方添加添加less-loader插件,
{
test: lessRegex,
exclude: lessModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2, // 注意
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'icss',
},
},
'less-loader' // 注意
),
sideEffects: true,
},
{
test: lessModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2, // 注意
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
modules: {
mode: 'local',
getLocalIdent: getCSSModuleLocalIdent,
},
},
'less-loader' //注意
),
},
(4.然后重启项目。(配置项一定要重启项目)
注意:less文件不能使用 “ // ” 直接注释css行样式
(5.打包后的static
因为打包后的文件里面包含重定向,需要代理访问,所以tomcat, apache,浏览器都不能直接解析,而nginx, iis可以解析。
修改webpack.config.js中的文件
删除static/前缀, 修改{ publicPath: '../../' } 为 { publicPath: '../' }
2.组件之间传值
(1.pops父子传值
1.父传子
父组件将状态信息或者数据标签属性的方式,传递给子组件,子组件通过props接收
// 父组件
<List todos={this.state.todos}/>
// 子组件
const {todos} = this.props;
2.子传父
父组件通过标签属性的方式将函数传递给子组件,而子组件调用父组件传递的函数进行数据的添加。
// 父组件
<Header addTodo = {this.addTodo}/>
// 子组件
// 调用父组件传递的函数,将输入的数据通过函数参数传递给父组件
this.props.addTodo({id:nanoid(),title:target.value,done:false})
3.单向数据流
父组件 的数据传递到子组件,反过来则不可以。子组件不能直接改变父组件的值,即使改变,浏览器也会发出警告。
会导致数据流向无法确认, 比如在子组件内 this.props.list = [] , 将出现异常
state状态一般用于当前组件数据,而props一般用于子组件数据,并且 一般使用map 修改数组数据, 使用filter删除数据
1.// 过滤map changeTodo = (id,done)=>{ // 获取原始数据 let {todos} =this.state; // 处理加工数据, 更改数据, 每次遍历会返回所有的值 let newTodos = todos.map((todo)=>{ if(todo.id === id){ return {...todo,done:done}; } return todo; }) this.setState({todos:newTodos}) }; 2. // 删除 filter deleteTodo = (id)=>{ // 获取原始数据 let {todos} = this.state; // 筛选数据 每次只返回结果为true时的数据 let newTodos = todos.filter((todo)=>{ return todo.id !== id }) // 更新数据 this.setState({todos:newTodos}) } 3. // 求和 reduce let doneCount = todos.reduce((pre,todo)=>{ return pre+(todo.done ? 1: 0) },0);
(2.pubsub-js兄弟传值
- 适用于任何组件之间的通信
- 需要在componentWillUnmount取消订阅。
安装模块 npm install pubsub-js
引入模块 import PubSub from 'pubsub-js'
发布信息
PubSub.publish('data',{isFirst:false,isLoading:true})
订阅信息
componentDidMount(){
// 订阅消息
// 可以先取消上一次的订阅
PubSub.unsubscribe("data")
this.token = PubSub.subscribe("data",(msg,stateObj)=>{
this.setState(stateObj);
console.log('list也收到订阅信息了',msg)
})
}
取消订阅
componentWillUnmount(){
// 取消消息订阅
PubSub.unsubscribe(this.token); // 从this中获取token数据
}
嵌套越多,会产生回调地狱。
3. 异步请求数据
(1.axios的使用
1.基本概念
- axios 轻量级,是封装xmlHttpRequest对象的ajax
- 是promise风格,返回的是一个promise对象
- 可以用在浏览器端和node服务器端
yarn add axios
// axios.get('http://localhost:3000/search/movies?q='+keyword).then(res=>{
// console.log(res.data)
// PubSub.publish('data',{movies:res.data,isLoading:false}); // 修改单独的数据
// },err=>{
// PubSub.publish('data',{err:err.message,isLoading:false}); // 修改单独的数据
// })
跨域是客户端收到前,被浏览器拦截,受到同源限制。
使用xml 的ajax引擎, 产生了同源策略。而使用同端口的代理服务器,就可以跨域访问。
2.变形axios
最重要的是在内部函数中 添加 **async ** 进行异步等待。
// axios 返回一个Promise对象, await可以将promise对象解析出来
useEffect(()=>{
const fetchData = async () => {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
setData(result.data);
};
fetchData();
},[])
(2.Fatch 的使用
XMLHttpReqquest
fatch是先与服务器获得连接,然后再获取数据。
// 使用fetch获取数据
// fetch('http://localhost:3000/search/movies2?q='+keyword).then(res=>{
// console.log("数据成功")
// return res.json();
// }).then(res=>{
// console.log("请求成功",res)
// }).catch(e=>{
// console.log("请求失败",e)
// })
// 先与服务器连接,再获取数据
try{
let response = await fetch('http://localhost:3000/search/movies2?q='+keyword)
let data = await response.json();
this.setState({isLoading:false});
}catch(err){
console.log("请求失败",err)
this.setState({isLoading:false})
}
(3.代理跨域访问(?)
在src文件下新建 setupProxy.js文件,复制以下代码搞定(这个文件会被自动识别,不用去导入)
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:8888/',
changeOrigin: true,
pathRewrite: {
'^/api': '',
}
})
);
app.use(
'/apc',
createProxyMiddleware({
target: 'http://localhost:7777/',
changeOrigin: true,
pathRewrite: {
'^/apc': '',
}
})
);
};
4.router路由的使用
1.SPA单页应用
单页应用只有一个页面,当页面资源加载完毕,页面将不会因为用户的点击而进行跳转。取而代之的是路由机制,实现网页内容的替换。 数据都要通过ajax异步获取。
2.router的使用
router是一个映射关系,可以根据 路由规则找到不同的函数或组件。
react-router是一个插件库,专门用户实现SPA应用
工作过程:当浏览器的path变为/test时,当前路由组件就会变为Test组件
3.路由原理
1. history模式, 直接使用h5推出history的api History.createBrowserHistory()
2. hash模式, (锚点) History.createHashHistory
4.安装模块
yarn add react-router-dom@5.1.0
最新版本有问题,所以确定一下路由版本
5.注意事项
-
Route和 Link标签都需要被BrowserRouter 包裹, 如果 路由 不在相同的 BrowserRouter, 则无法自由切换组件。不同的路由器是属于不同的模块.
-
建议在index.js中使用同一个BrowserRouter包裹, 因而下面的所有组件就会在同一个路由器中,不仅保证了组件的动态切换特性,也减少了代码的冗余性
基本使用
(1.Link路由导航
-
在多页应用中,使用a标签 节切换不同的页面
-
在单页应用中,就不允许使用a标签进行跳转。 在react使用 "Link’ 标签编写路由链接, 因为它最后会转化为a标签的形式。
import {Link} from 'react-router-dom';
// 路由链接
<Link to="/home" >首页</Link>
(2.Route路由规则
“Route” 标签 用于展示导航指向的组件内容
import {Route} from 'react-router-dom';
//绑定路由或者注册路由
<Route path="/home" components={Home}></Route>
(3.BrowserRouter注册路由
区别:BrowserRouter和HashRouter模式
-
底层原理不一样:
-
BrowserRouter 使用H5的history APi,不兼容IE9 及以下的版本
-
hashRouter使用的是URL
-
-
path表现形式不一样
- HashRouter的路径包含#
-
刷新 后对路由state参数的影响
-
BrowserRouter没有任何影响 ,因为state保存在history对象中
-
HashRouter刷新后会导致路由State参数的丢失!
-
(4.NavLink 高亮路由链接*
活动的链接都会添加默认的active类名,可以通过 activeClassName指定活动时 显示的类名
注意: NavLink只会监听路径的变化 ,params传参可以启动。 但search传参,则认为是相同的路由。
import {NavLink} from 'react-router-dom';
{/*默认会给激活的路由链接添加active 类名*/}
<NavLink className="active" to="/home">首页</NavLink> |
{/*也可以自定义链接, 使用activeClassName来显示活动时的类名*/}
<NavLink activeClassName="my-active" to="/home">首页</NavLink> |
<NavLink activeClassName="my-active" to="/about">首页</NavLink> |
4.封装NavLink组件
-
标签体的内容可以作为 一个特殊的props传递给组件,key是children, value是标签体的内容
-
组件内可以通过this.props.children获取标签体的内容
---- app.js组件
{/*标签体的内容是一个特殊的属性children*/}
<MyNavLink to="/home" a={1} b={2}>Home</MyNavLink>
<MyNavLink to="/about" >about</MyNavLink>
----自定义MyNavLink组件
// 可以动态向子组件传入属性属性
// NavLink 会被转化成超链接a标签, children属性值会自动填充到NavLink标签体中
<NavLink activeClassName="my-active" {...this.props}/>
(5.Switch单路由匹配机制
-
一个路由规则,对应多个组件。 这样会导致严重的效率问题
-
使用Switch标签包裹所有的路由规则,只会匹配首个单独的组件。
-
Switch有效地提高了react运行的效率。
import {Switch} from 'react-router-dom';
<Switch>
<Route path='/home' component={Home}></Route>
<Route path='/about' component={About}></Route>
<Route path='/about' component={Test}></Route>
{/*编写路由规则 或者 注册路由*/}
</Switch>
(6.DocumentTitle标题模块
安装模块
yarn add react-document-title
使用模块
import DocumentTitle from 'react-document-title'
<Suspense fallback={<h1>Loading</h1>}>
{
routerMap.map((item,index)=>{
return (
<Route path={item.path} component={item.component} exact={item.exact} key={index}> </Route> )
})
}
</Suspense>
// 对当前的总页面进行设置
function Home(){
return (
<DocumentTitle title={activeTitle ? activeTitle : defaultTitle}>
....
</DocumentTitle>
)
}
export default Home;
// 监听当前的路由变化(改变标题)
const [activeTitle,setActiveTitle] = useState(null)
const defaultTitle = '山口岩智能水库云平台'
useEffect(()=>{
let route = window.location.href;
subRouterMap.some((item,index)=>{
if(route.endsWith(item.path)){
setActiveTitle(item.title)
}
})
},[location])
(7.withRouter路由转化组件
- 一般组件 没有 this.props.history相关的api, 它没有经过 的 使用。
- 路由组件 才具有 histroy, location, match 这三个参数
- withRouter可以加工 一般组件,让一般组件 具备路由组件所特有的api
- withRouter是一个函数, 接收一个一般组件 ,返回一个加工后的新组件,这个 组件具备路由组件的一些特性
引入withRouter
import {withRouter} from 'react-router-dom';
使用withRouter
export default withRouter(Header);
(8.lazy+Suspense懒加载
lazyload懒加载,是对路由引入模块的时机进行控制。
-
需要从react 包中引入lazy 和 Suspense 方法
-
laay动态引入, **
Suspendse
**包裹路由规则,定义响应期间的回调。 -
import React,{lazy,Suspense} from 'react'; const Home = lazy(()=>import('./Home')) const About = lazy(()=>import('./About')) // 懒加载 <Suspense fallback={<h1>Loading</h1>}> <Route path='/home' component={Home}></Route> <Route path='/about' component={About}></Route> </Suspense>
(9.Redirect路由重定向
- 当所有内部都没有匹配到结果的时候,就会重定向目标路由。
- 一般放在路由规则的底部
import {Redirect, Link} from 'react-router-dom'
1.标签式重定向
<Redirect to="home"/>
2.history编程式重定向
this.props.history.push('/home/')
vue中使用的是
router.push({}) 或者 $router.push({ })
path:"/:pathMatch(.*)"
component:ErrorPage 全局的重定向。
(10.push编程式路由(props)
-
在js中进行路由的跳转
-
绑定的事件向函数传入参数,如果需要传入参数,则需要将回调函数传入参数中,否则 会自动执行;
-
如果不需要传入参数,可以直接填写函数名称
跳转函数
pushShow = (id,title)=>{
// 进行push跳转
// params传参
// this.props.history.push(`/about/message/detail/${id}/${title}`)
// search传参
this.props.history.push(`/about/message/detail?id=${id}&title=${title}`)
// state传参
// this.props.history.push(`/about/message/detail`,{id,title})
}
replaceShow = (id,title)=>{
// 进行push跳转
this.props.history.replace(`/about/message/detail/${id}/${title}`)
}
// 后退
goBack = ()=>{
this.props.history.goBack();
}
// 前进一步
goForward = ()=>{
this.props.history.goForward(-2);
}
// 前进( n / -n)
go = ()=>{
// this.props.history.go(2);
this.props.history.go(-2);
}
事件触发
<button style={{'color':'red'}} onClick={()=>{this.pushShow(item.id, item.title)}}>push跳转</button>
<button style={{'color':'red'}} onClick={()=>{this.replaceShow(item.id, item.title)}}>replace跳转</button>
<button onClick={this.goBack}>后退</button> // 不传入参数,可以不使用回调函数
<button onClick={this.goForward}>前进</button>
<button onClick={this.go}>Go</button>
区别:push与replace模式
- 堆栈的思想, 默认push方式
- 新的链接会被压入栈中,成为栈顶元素
- replace是替换当前在缓存中保存的路由路径。
- 在路由链接中使用 replace={true} 或者 replace 转换为replace模式
(12.useLocation 路由监听
前提: 安装react-router-dom@ 5.1.0 版本,这是新特性
import { useLocation } from 'react-router-dom';
const location = useLocation();
// 监听路由的变化 。
useEffect(()=>{
// 从对象字符串中转换成对象
let search = qs.parse(props.location.search.slice(1))
let {title} = search;
setTitle(title)
console.log("SelfList路由数据",props,title)
console.log("pathname is",obj)
if(title=='默认收藏' || title == ''){
setMusicList(getMusicList)
}else{
setMusicList(getMusicList2)
}
},[location.search])
(13.Route的exact属性
(1.exact精确匹配
模糊匹配:
- 路由链接尾部可以添加更长的路径
- 路由规则是只有头部相同,但没有对应的精确路径。
默认模糊匹配
<MyNavLink to="/about/abc" >about</MyNavLink>
<Route path='/about' component={About}></Route>
精确匹配:
-
使用exact开启精确匹配 exact={true} 或者 exact。
-
一般只有首页才会使用精确匹配“/”
-
只有路由链接和路由规则相匹配的结果才能正常返回结果
-
建议不使用开启严格匹配,有时候开启会导致无法匹配二级路由
<Route exact={true} path='/home' component={Home}></Route>
(2.嵌套路由(二级)
- 二级路由必须先匹配到上一级路由。
- 二级路由是层层嵌套,上一级使用精确匹配会导致无法匹配二级路由。
<div className="nav">
<MyNavLink to="/about/new">news</MyNavLink>
<MyNavLink to="/about/message">Message</MyNavLink>
</div>
{/*注册路由*/}
<Switch>
<Route path="/about/new" component={News} />
<Route path="/about/message" component={Message} />
<Redirect to="/about/new"/>
</Switch>
(3.map后台动态路由
-
{ } 说明里面是一个Js语法 (包含变量) ,key = {index}
-
而vue中 v-bind也说明字符串代表的是一个变量 v-bind:key = “index”
let routeConfig =[
{path: '/' ,title: 博客首页' ,exact:true, component:Index},
{path : '/video',title: '视频教程' , exact:false, component:Video},
{path : '/workplace',title:'职场技能' ,exact:false, component:Workplace},
]
<ul>
{
routeConfig.map((item,index)=>{
return (<li key={index}> <Link to= {item.path} >{item.title} >)
})
}
</ul>
<div className = "rightMain">
{
{/*router-view*/}
routeConfig.map((item,index)=>{
return (<Route key={index} path={item.path} component={title}>)
})
}
(4.Fragment模板标签
-
react包中有**
React, React.Component
**React是声明当前组件是基于React的组件
-
react-dom包有**
ReactDom
**ReactDom是将组件挂载到页面的模块
一般的组件中,只能返回一个单独的标签,而使用**
弹性布局
**的时候则会出现多余的标签。在react包中有**
React.Fragment
** , 可以去除最外层标签。使用标签代替
标签,但前者标签不会挂载到DOM上,和vue的template一样的功能
创建组件
import React,{Component,Fragment} from 'react';
class App extends Component{ // 继承React.Component组件
render(){
return (
// <div> {/*只能被 一个单独的标签包裹div */}
// <h1>66666</h1>
// <p>Hello World</p>
// </div>
<Fragment> {/*只能被 一个单独的标签包裹div */}
<h1>66666</h1>
<p>Hello World</p>
</Fragment>
)
}
}
export default App;
挂载DOM
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<App />,
document.getElementById('root')
);
(14.路由跳转传参
(1.params路径参数
- 路由规则使用 :id 或者 :xx
- 路由链接,可以使用“+” 连接路径 ,但需要使用{} 包围。 (一般数字是要{}包围的)
- 目标路由组件,可以使用this.props.match.params.xxx 获取传入的参数
{/*路由链接*/}
<p key={item.id}>
<Link className="nav-dark" to={`/about/message/detail/${item.id}/${item.title}`}>{item.title}</Link>
</p>
{/*<p key={item.id}>
<Link className="nav-dark" to={"/about/message/detail/"+item.id+"/"+item.title}>{item.title}</Link>
</p>*/}
{/*注册路由*/}
<Route path="/about/message/detail/:id/:title" component={Detail}/>
{/*目标路由组件*/}
console.log("数据是",this.props.match.params.id);
console.log("数据是",this.props.match.params.title);
let {id,title} = this.props.match.params
(2.路由间search参数
-
传入的参数,会在子组件的lacation中出现
-
query参数一般会叫做 search参数, 它只是? 后面的字符串
-
内置querystring 可以将字符转换成 对象形式。 (并非是JSON.stringify)
-
search传入 的参数
-
qs.parse 将串行化的字符串转换成对象。 (qs.stringify 功能相反)
-
JSON.parse 是将 类原对象字符串转换成对象。
-
注册路由
to={`/about/message/detail?id=${item.id}&title=${item.title}`}
-
获取到的search是urlencoded 编码字符串
-
引入querystring模块
yarn add querystring import qs from 'querystring';
-
将串行化的字符串转换成对象
let search = qs.parse(this.props.location.search.slice(1)); let {id,title} = search;
(3.路由间state参数
-
state参数存在于缓存中。
-
只有在成功访问后,才会保存这个状态在浏览器中。
-
直接访问是获取不到state参数。
-
一般用于传递比较 敏感数据
-
注册路由
to={{pathname:'/about/message/detail',state:{id:item.id,title:item.title}}}
-
获取参数
let {id,title} = this.props.location.state;
(4.react,vue的比较
1.路径传值 ,props获取数据
// 获取传值数据 let tempId = this.props.match.params.id;
<Link to={'/list/'+item.cid}> {item.title}
// 定义路由规则, 主路由需要 <Router>包围 <Route path = '/list/:id' component={List}></Route>
// 可以在加载模板后获取
componentDidMount(){
console.log(this.props)
let tempId = this.props.match.params.id;
this.setState({id:tempId}) // 把数据放在state里面
}
<h1>List-page:{this.state.id}</h1>
2.vue中的router-link 与react的Link
// vue
<li> <router-link :to="{path:'/about/article',query:{name:'hello',age:100}}">文章二</router-link></li>
// react
<Fragment>
<h1>作者编号{this.state.author_id}</h1>
<ul> { /**这其实就像v-for */}
{
this.state.list.map((item,index)=>{
return (
<li key={index}> {/*一定要传入key*/}
<Link to={'/list/'+item.cid}> {item.title}</Link>
</li>
)
})
}
</ul>
</Fragment>
3. vue中的reactive({ }) 和react的 state
// vue
let goods = reactive({ data:[] })
// react
constructor(props) {
super(props);
this.state = { /* 类似 reactive({}) */
list:[
{cid:111,title:"你好美,小美"},
{cid:222,title:"你好胖,小胖"},
{cid:333,title:"你好懒,小懒"},
{cid:444,title:"你好聪,小聪"},
]
}
}
5.redux的使用
1.redux是一个专门 用于做状态管理的JS库(不是react插件库),它可以用在react, angular,vue等项目中,基本与react配合使用。 一个组件的状态,多个组件需要共享使用的情况。
2.相当于vuex 的,是集中式管理react应用中多个组件共享的状态 。因为redux是一个重量级的js库,使用原则是: 只要系统不是非常复杂,就尽量不用。
(1.redux的原理
(1.流程: redux是维护状态,用于集中式管理。
React Components 向Action Creators 进行登记,
Action Creators 向Store 进行提交申请。
Store 告诉 Reducers 去完成这个行为。
Store 收到 Reducers 处理的结果,再去告诉React Components。
(2.三要素: action, store, reducer
(1. action : 动作的对象
包含两个属性,type 标识属性和 data数据属性
(2.reducer : 用于初始化数据,和加工状态
加工时,将旧的previousState 转换成新的 state
(3.store: 存储状态
用于将Action Creators ,store, Reducers连接在一起的对象
(3.安装模块
yarn add redux redux-thunk react-redux
注意:安装完模块后,重新启动项目
(4.使用总结:
-
虽然容器组件是用于中间交互,但处理数据存储操作的就是窗口组件。
-
异步性:redux中进行加工操作, 在回调函数中提交更新状态。
-
隔绝性:在redux中的reducer不能去获取其它的状态数据.
-
可操作性 :根据特定的参数,在UI组件中执行刷新操作
-
可继承性 :redux只能在特定的hooks中使用,也可以用于传递给子组件,但允许直接显示在UI组件上。
-
共享性:可以用于处理当前组件状态state更新过慢的问题
(2.store与reducer(原生 )
处理器: count_reducer.js的文件内容,用于提供服务,操作数据
注意:不要把逻辑业务放在reducer中
import React from 'react';
// 初始化值
const initData = 0
// count组件reducer,只为count提供服务。本质是函数
// 有点类似于reduce 进行示例运算
function CountReducer(preState=initData,action) {
// action的依赖于type和data属性
console.log(preState,action)
let {type,data} = action
switch (type) {
case 'increment':
return preState + data;
case 'decrement':
return preState - data;
default :
return preState;
}
}
export default CountReducer;
**存储仓库:**store.js的文件,用于创建事务,写入记录
在 createStore调用时,要传入一个为其服务的reducer,
import React from 'react'
import {createStore} from 'redux';
// createStore 创建了store
import CountReducer from './count_reducer'
// 引入 reducer , 帮助store处理操作
const store = createStore(CountReducer);
export default store;
**视图组件:**count.js使用redux,组件监听redux
componentDidMount() {
// 监听redux,当state改变时,重新更新页面
store.subscribe(()=>{
this.setState({}) // 不能使用this.render() 进行重新渲染。
})
}
increment = ()=>{
// select的值
let {value} = this.selectElement;
// 通知redux,执行加法
store.dispatch({type:'increment',data:value*1})
}
<h3>计算结果为:<span>{store.getState()}</span></h3>
<button value="+" onClick={this.increment}>+</button>
store优化:全局监听redux
// 监听redux,当state改变时,重新渲染组件。
store.subscribe(()=>{
ReactDOM.render(
<App />,
document.getElementById('root')
);
})
使用流程
components监听redux, 创建ActionCreators对象。
components通知ActiontCreators, 并向store提交action
store将action传递给reducer。
store.dispatch({type:'decrement',data:value*1})
reducer进行数据的初始化和加工。
store获取返回的状态,并将结果返回给组件,重新渲染页面。
store.getState()
reducer处理
reducer的本质是一个纯函数,用于接收两个参数PreState,action, 然后返回加工后的状态
reducer 有两个作用: 一是初始化状态,二是加工状态
reducer被第一次调用时,是sotre自动触发的, 传递的preState是Undefined, 传递的action是
{type:'@@REDUX/INIT_c.x.1.4}
redux的版本信息reducer为什么会被调用再次,因为创建一个活动, 向store分发一个活动的时候,store会将action提交给reducer, 这个时候就会被初始化,然后数据处理,又再次调用了一次。(??)
Action优化:将活动分离出组件
/*
* 为Count组件生成action对象
* */
import {INCREMENT,DECREMENT} from './constant';
// 加法
export const createIncrementAction = data=>({type:INCREMENT,data:data});
// 减法
export const createDecrementAction = data=>({type:DECREMENT,data:data});
/*
* 用于存放action对象中使用的type的值,
* */
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';
(3.redux-thunk异步(原生)
把异步操作放在创建action的文件里。
store只接收一般对象,如果需要使用异步,则需要安装中间件 redux-thunk
yarn add redux-thunk
异步:store使用thunk中间件,以执行异步
// 使用redux提供的createStore函数来创建store
import {createStore,applyMiddleware} from 'redux';
// 引入reducer,用于store处理操作
import CountReducer from './count_reducer'
// 引入thunk中间件,用于让store支持异步action
import thunk from 'redux-thunk';
// 引入 reducer , 帮助store处理操作
const store = createStore(CountReducer,applyMiddleware(thunk));
export default store;
原count_action.js ,添加以下内容
import store from './store'
// 用于action的异步
export const createIncrementAsyncAction = (data,time)=>{
return ()=>{
setTimeout(()=>{
store.dispatch(createIncrementAction(data))
},time)
}
}
(4.combineReducers(原生)
- 不同组件组成的容器,可以通过redux共享数据
- Person的reducer和Counter的reducer需要经过
combineReducers
进行合并,合并产生后的总状态是一个对象 - 传递给store的是总的reducer,在容器组件中获取状态的时候, 也是需要指定key获取.
// 使用redux提供的createStore函数来创建store
import {createStore,applyMiddleware,combineReducers} from 'redux';
// 引入reducer,用于store处理操作
import CountReducer from './reducers/count'
import PersonReducer from './reducers/person'
// 引入thunk中间件,用于让store支持异步action
import thunk from 'redux-thunk';
// 汇总所有的reducer
const allReducers = combineReducers({
he:CountReducer,
rens:PersonReducer
})
// 引入 reducer , 帮助store处理操作
export default createStore(allReducers,applyMiddleware(thunk));
(5.react-redux的原理(新)*
**使用原理 **
首先在容器中注册UI组件,然后可以通过容器组件间接访问UI组件。
-
UI组件连接容器组件, 容器组件与redux交互,
-
容器组件向action Creator 创建提交创建申请,
-
action被提交到store中,进行集中管理状态,然后通知reducer去处理action,
-
reducer对数据进行初始化或者加工后,返回一个新的状态。
-
store将新的状态进行存储,而容器组件可以获取新的状态。
-
UI组件再向容器组件获取状态。
安装模块
yarn add react-redux
向UI组件中传入store对象
<Count store={store}/>
容器组件: 定义一个Containers容器,Count.jsx
容器用于与redux交互,提交操作方法,返回保存的状态, 简化了redux的一系列操作
// 引入connect ,用于连接UI和redux
import {connect} from 'react-redux';
// 引入Count UI组件
import CountUI from '../Components/Count';
// 接收redux中的状态参数, 传递redux的状态给UI组件
// 映射状态到props
function mapSateToProps(state){
return {count:state}
}
// 函数返回的对象作为 操作状态的方法传递给UI组件
// 映射dispatch到props
function mapDispatchToProps(dispatch){
return {
jia:(data)=>{
// 修改redux中的状态
dispatch({type:'increment',data})
},
}
}
// 使用connect 连接Count UI组件, 并把创建的容器组件暴露出去
export default connect(mapSateToProps,mapDispatchToProps)(CountUI)
**UI组件:**直接使用容器通过 Props传递的方法Component, Count.jsx
// 调用父组件传递过来的方法
this.props.jia(value*1);
// 调用父组件传递过来的状态
this.props.count
容器组件优化: Container容器的优化
容器组件会自动监听redux的变化 ,所以不需要在全局监听redux
// 引入connect ,用于连接UI和redux
import {connect} from 'react-redux';
// 引入Count UI组件
import CountUI from '../Components/Count';
// 引入action
import {createIncrementAction,createDecrementAction,createIncrementAsyncAction} from '../redux2/count_action'
export default connect(
//接收一个参数,这个 参数就是redux中的状态, 传递redux的状态给UI组件
state => ({count:state}),
// 返回的对象作为操作状态的方法传递给UI组件
// 第1种方式: 传函数 (默认方式)
dispatch=>({
jia:data => dispatch(createIncrementAction(data))
}),
// 第2种方式,传对象(建议)
{
jia:data=>createIncrementAction(data),
// 这里虽然直接写action,但react-redux会自动调用 dispatch 派发action
jia:createIncrementAction,
}
)(CountUI)
store优化: 全局的Provider
react-redux中还提供了Provider。只要容器中需要使用store,被Provider包裹的组件,就可以通过Provider组件提供store。
import {Provider} from 'react-redux';
import store from './redux2/store'
<Provider store={store}>
<App />
</Provider>,
(5.reduce纯函数的规则*
纯函数的规则:
-
即始终返回相同的值,无论什么时候调用,比如Math.cos(0)就是纯函数,Math.random()就不是纯函数
-
返回结果只依赖于它的参数,不改变参数的值,不使用外部变量
-
执行过程里面没有副作用,比如修改外部变量,请求,DOM APl, console.log都不行
为什么要使用纯函数?
- 靠谱,不会产生不可预料的行为,也不会对外部产生影响,更容易调试,易于组合,易于并行开发
- Redunx中的Reducer必须是一个纯函数
- 参数一定时,始终返回相同的值。
- 不改变参数的值,不使用外部的变量
- 无副作用。不改变外部的变量,也不请求数据和输出。
(6.redux的开发运维
(1.谷歌开发工具(原生)
yarn add redux-devtools-extension
-
并且也需要在store.js中引入对应的扩展插件
-
// 引入 redux-devtools-extension import {composeWithDevTools} from 'redux-devtools-extension'; // 引入 reducer , 帮助store处理操作 export default createStore(allReducers,composeWithDevTools(applyMiddleware(thunk)));
(2.优化代码
- 所有变量名命名规范,尽量触发对象的简写形式
- reducer.js中编写index.js用于汇总并暴露所有 的reducer
(3.本地查看build项目
yarn global add serve
serve -s build
6.Hooks的使用
react 16.8 以上才可以使用
(1.类式组件
import React, { Component, } from 'react';
class Example extends Component {
constructor(props) {
super(props);
this.state = {count:0 }
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.addCount.bind(this)}>Click me</button>
</div>
);
}
addCount(){
this.setState({count:this.state.count+1})
}
}
export default Example;
(2.函数式组件
import React, { useState } from 'react';
function Example(){
const [count, setCount] = useState(0); //解构赋值
let _useState = userState(0) // 真实的内部结构
let count = _useState[0]
let setState = _useState[1]
return (
<div>
<p>You clicked {count} times</p>
<button onClick={()=>{setCount(count+1)}}>Click me</button>
</div>
)
}
export default Example;
(1.useState路由状态
用于保存组件中的状态。功能同setState
import React,{useState} from 'react';
let [count,setCount] = useState(0);
setCount(count+1) // 第一种写法:直接写出更新的状态值
setCount(count => count+1) // 第二种写法:回调函数,是对象形式的简写
// 一般用于连续变化数据的时候使用。
(2.useEffect生命周期
处理功能
- 发送ajax请求获取数据
- 设置 订阅、启动定时器
- 手动惠以真实DOM
用于模拟生命周期
useEffect会在模板挂载到页面上,会自动执行。
- componentWillMount , 可以添加[] 替换
- componentWillUpdate 可以不添加第二个参数
- componentWillUnmount 可以返回一个函数,在卸载之前执行。
useEffect(()=>{
let timer = setInterval(()=>{
setCount(count=>count+1)
},500)
// 返回一个函数,在组件制裁之前执行,类似于WillUnmount
return ()=>{
clearInterval(timer)
}
},[])
// 第二个参数用于监听数据。
// 传入第二个参数,类似DidUpdate, 监听指定的状态
// 不传入第二个参数,类似于DidMount,所有状态都不监听
const unmount = ()=>{
ReactDom.unmountComponentAtNode(document.getElementById("root"))
}
- 可以监听的数据: 相当于vue的watch
- props传递的数据‘
- location. 路由的变化
- 当前参数的变化。
- 路由的变化, 只后于模块加载,所以可以用于监听
- 对象的响应式,需要提前设计结构。
const [content,setContent] = useState({ recommend:[], selfmusic:[], selflist:[] });
- 监听路由 / 传递props
useEffect(()=>{ // 改变路由 getInitData(); console.log("111",content) },[obj.pathname]) useEffect(()=>{ // 改变路由参数。 getInitData(); console.log("111",content) },[props])
异步函数
const getInitData = async()=>{ // 可能没有效果,但用于axios有效
const sidebar = [title:‘111’]
setContent(sidebar)
}
(3.useRef绑定节点
可以在函数组件中存储、查找组件内的标签或者任意其它数据.
保存标签对象,功能与 React.createRef()
一样
import React,{useState,useEffect,useRef} from 'react';
const myRef = useRef(); // 声明一个ref变量,用于保存标签对象
<input type="text" ref={myRef}/> // 在标签内使用ref绑定变量
console.log(myRef.current.value) // 显示结果
(4.useMemo*函数优化
**作用:**用于优化组件之间的功能。
memo` 的用法是:`函数组件里面的PureComponent
优化之后的子组件
function Button({ name, children }) {
// 执行函数
function changeName(name) {
console.log('11')
return name + '改变name的方法'
}
// 与useEffect功能类似。
const otherName = useMemo(()=>{
return changeName(name)
},[name])
return (
<>
<div>{otherName}</div>
<div>{children}</div>
</>
)
}
(5.useCallback*回调函数
// 页面加载后的回调函数。 (常用于验证码功能)
const handleClick = useCallback((captcha) => {
console.log('captcha:', captcha);
}, []);
(6.useReducer(略)
(7.useContext(略)
(8.hooks中使用事件监听
// 监听对应节点的变化
useEffect(() => {
window.addEventListener("click", function() {
console.log('222222222222226666');
});
return () => {
window.removeEventListener("click");
};
}, [window]);
不建议在页面中监听window事件, 毕竟,这会导致很大的性能浪费。
也不利于处理全局的window事件。
注意: 不能给一个空节点注册监听事件。
6.高级扩展内容
(1.Context (class)祖辈传值
组件之间的通信方式,一般用于祖先组件和后代组件。
-
创建Context容器对象
-
const MyContext = React.createContext() const {Provider,Consumer} = MyContext;
-
渲染组件时,外面包裹Provider,通过 value属性传递给后代组件数据
-
<Provider value = {{username,age}}> <B/> </Provider>
-
后代读取数据
-
<Consumer> { value => `${value.username} ,${value.age}` } </Consumer>
(2.PureComponent©性能优化
继承PureComponent,可以自动对组件内的数据进行比较,提高效率。
1.优化的来源
- 只要执行setState(),即使不改变状态数据,组件也会重新render() ==>效率低
- 只当前组件重新render(),就会自动重新render子组件,即使子组件没有用到父组件的任何数据==>效率低
- 目标:只有当组件的state或props数据发生改变时才重新render()
- 因为Component中的 shouldComponentuUpdate()总是返回true
2.生命周期优化
父组件优化: 只有状态改变时,才会更新,进行重新渲染。
shouldComponentUpdate(preProps,preState){
console.log("现在的",this.props,this.state)
console.log("要改变的",preProps,preState)
if(this.state.Like === preState.Like){ // 如果状态没有发生改变,就不更新
return false;
}
return true
}
子组件优化:父组件更新,只要子组件没有获得新的数据,就不会执行更新
shouldComponentUpdate(preProps,preState){
// console.log("现在的",this.props,this.state)
// console.log("要改变的",preProps,preState)
if(this.props.Like === preProps.Like){ // 如果状态没有发生改变,就不更新
return false;
}
return true
}
3.继承PureComponent优化
class xxx extednds PureComponent{
// PureComponent 使用浅比较,对象属性更新改变不会重新,会返回false
let obj = this.state;
obj.Like = "足球"
this.setState(obj);
}
(3.ErrorBoundary©错误捕获
(1. 背景
错误边界是一种 React 组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI。
错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。
(2.问题
react
强调了只有上面 3 种实现下才会捕获错误。
无法自动捕获下面 4 种实现
- 事件处理
- 异步代码(例如
setTimeout
、Promise
回调函数) - 服务端渲染
- 它自身抛出来的错误(并非它的子组件)
通常希望业务代码能够复用 ErrorBoundary
的错误处理逻辑。
(3.实现捕获
如果要 ErrorBoundary
能处理业务代码的自定义错误,只要在渲染期间抛出错误即可。
子组件获取数据错误 ,可以在父组件 使用 getDerivedStateFromError
获取错误信息,并把错误控制在一定的范围内。
- 将错误控制在一定范围内。捕获后代的错误
- 使用
getDerivedStateFromError
获取错误信息。 componentDidCatch
用于记录错误数据。
class方法
//生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
//在render之前触发/返回新的state
return {
hasError: true,
}
}
componentDidCatch(error,info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
)
hook方法
function useErrorHandler() {
const [error, setError] = React.useState(null);
if (error != null)
throw error;
return setError;
}
function Foo() {
const handleError = useErrorHandler();
fetchMayError()
.catch(handleError);
return <div></div>;
}
(4.组件间通信方式
1.组件间的关系∶
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
2.几种通信方式:
-
props:
i. children propsii. render props
-
消息订阅-发布:pubs-sub、event等等
-
集中式管理:redux、dva等等
-
conText:生产者-消费者模式
3.比较好的搭配方式:
- 父子组件:props
- 兄弟组件:消息订阅-发布、集中式管理
- 祖孙组件(跨级组件)︰消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
7.项目开发
(1.antd组件库
- 安装ant组件
npm install antd -S
- 使用ant组件
(1).antd普通组件
import 'antd/dist/antd.css'; // 引入样式(或者main.js中引入)
import { Button,DatePicker } from 'antd'; // 引入组件库
<Button type="primary">Primary Button</Button>
<DatePicker onChange={onChange} />
<RangePicker picker="date" />
(2.ant-design/icons图标
安装模块
yarn add @ant-design/icons
使用图标
import { WechatOutlined } from '@ant-design/icons'; // 引入图标
// 查询到目标组件后,直接粘贴
<WechatOutlined />
(3). iconfont 阿里云组件
——main.js中引入css文件
import 'antd/dist/antd.css';
——新建立一个IconFont.jsx文件,添加以下内容
import { createFromIconfontCN } from '@ant-design/icons';
export const IconFont = createFromIconfontCN({
scriptUrl: 'https://at.alicdn.com/t/font_3062734_0of12bnln1jr.js',
//scriptUrl: '/font/iconfont.js',
});
——引入自定义组件
import { IconFont } from 'iconfont/Iconfont';
——使用iconfont组件
<IconFont type='icon-warning' />
如果下载到本地,也是类似操作.但需要把字体文件放在public文件夹下
把项目下载到本地,并放在public文件夹下,建立font文件夹夹
把引入文件路径修改为
import { createFromIconfontCN } from '@ant-design/icons';
export const IconFont = createFromIconfontCN({
// scriptUrl: 'https://at.alicdn.com/t/font_3062734_0of12bnln1jr.js',
scriptUrl: '/font/iconfont.js',
});
3.按需引入 (暂不考虑)
需要对create-react-app脚手架进行配置
1. 安装模块
npm install react-app-rewired customize-cra
2.修改 package.json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
3.安装模块
npm install babel-plugin-import
4. 项目下新建立 config-overrides.js
const {override, fixBabelImports} = require('customizecra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
);
暂时修改不了主题色
【React精品】React全家桶+完整商城后台项目#UniJS#Antd Pro#全程实录(已完结)_哔哩哔哩_bilibili
(3.zarm组件库(略)
(1.安装模块
yarn add zarm
(2.使用组件
(1).zarn普通组件
——在main.js中引入css
import 'zarm/dist/zarm.css'; // 引入css (或者在main.js中引入)
——使用组件
import {Button } from 'zarm';
<Button theme="primary">Hello World!</Button>
(4.customize-cra根目录
- 方法一: 初始化项目后使用命令yarn eject(不建议)
只有在开始建立项目的时候, 运行 **yarn eject ** 或者 npm run eject, 就会在项目下生成config文件夹,
该文件下有一个webpack.config.js 文件 , 向 alias 选项中添加 '@': path.resolve('src'),
然后,可以在项目下使用 @ 表示 根路径
如:
import Hello from '@/First/Second'
注意:单独复制config无效, 已经有自定义文件的项目无法使用命令。
如果在gitee的本地仓库中,可能需要先提交,再使用命令。
- 方法二:通过安装customize-cra插件来实现(建议)
当方法一失效或者不利于重新建立项目 时,可以采用此方法。
安装模块:
yarn add custom-cra react-app-rewired
- 在根目录新建config-overrides.js, 与package.json同级
const {
override,
addWebpackAlias
} = require('customize-cra')
const path = require('path')
module.exports = override(
// 路径别名
addWebpackAlias({
'@': path.resolve(__dirname, 'src'),
'@c': path.resolve(__dirname, 'src/components'),
'@views': path.resolve(__dirname, 'src/views'),
'@utils': path.resolve('src/utils'),
'@styles': path.resolve('src/styles'),
})
)
- 修改package.json文件
原本之前的
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
修改之后
scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
- 这边保存重启一下即可
注意:路由懒加载必需使用相对路径,当前设置的根路径无效
(5.react-transition-group
yarn add react-transition-group@4.1.1
功能相当于使用animation
import {CSS}
<CSSTransition in={this.state.isShow}
timeout={2000}
classNames="boss-text"
unmountOnExit // 删除节点
>
<div>大魔王</div>
</CSSTransition>
.boss-text-enter{opacity:0;color:blue} /*从隐藏到显示*/
.boss-text-enter-active{opacity:1; transition:opacity 2000ms;}
.boss-text-enter-done{opacity:1; }
.boss-text-exit{opacity:1; color:red} /*从显示到退出*/
.boss-text-eixt-active{opacity:0; transition:opacity 2000ms;}
.boss-text-eixt-done{opacity:0;}
控制多个模块(包括新生成的)
import {CSSTransition,TransitionGroup} from 'react-transition-group'
<ul ref={(ul)=>{this.ul=ul}}>
<TransitionGroup>
{
this.state.list.map((item,index)=>{
return (
<CSSTransition
timeout={2000}
classNames="boss-text"
unmountOnExit
>
<XiaojiejieItem
key= {index+item}
content={item}
index = {index}
deleteItem = {this.deleteItem.bind(this)}
/>
</CSSTransition>
)
})
}
</TransitionGroup>
</ul>
8.项目上线
(1.项目根目录
- https://localhost:3000/css/xx
- /css
- /%PUBLIC_STAIC%/css
注意: 开发者模式下,css是以内联样式的形式加载的。
生产模式下,css才会打包成单独的css文件
原react-scripts/ webpack.config.js内容 isEnvDevelopment && require.resolve('style-loader'), isEnvProduction && { loader: MiniCssExtractPlugin.loader, // css is located in `static/css`, use '../../' to locate index.html folder // in production `paths.publicUrlOrPath` can be a relative path options: paths.publicUrlOrPath.startsWith('.') ? { publicPath: '../../' } : {}, },
(2.项目布署
yarn start 测试项目
yarn build 打包项目
本地使用,可能不会出现异常,但会出现警告。服务器上遇到无法运行,则可以使用以下方案。
注意:如果出现**
[react-scripts: command not found after running npm start
**,则考虑使用npm。
-
先删除原有的
package-lock.json
和 node_modules -
使用以下命令
-
npm install 安装模块 npm run start 测试项目 // 在服务器上依旧无法访问网站 npm run build 打包项目
-
正常情况下,打包问题可以完美解决,但运行测试还是不行。
最后,注意一下,react是否会出现与vue一样的路由问题,即刷新导致页面丢失的问题
location / {
index /www/wwwadmin/myblog-project/blog_react/build/index.html; ### 首页
try_files $uri $uri/ /index.html;
### 将页面重定向index.html, 将路由交给前端处理。
}
运行结果: session会话设置失效,导致登录模块失效。