1. React 简介
React是什么
React是一个将数据渲染为HTML视图的开源JavaScript库 只关注操作DOM呈现页面的工作
为什么要学React(原生js的缺点)
- 原生JavaScript操作DOM繁琐,效率低(DOM-API操作UI)
- 原生JavaScript直接操作DOM,浏览器会进行大量的重绘重排
- 原生JavaScript没有组件化的编码方案,代码复用率低
React的特点
- 采用组件化模式,声明式代码,提高开发效率及组件复用率
- 在React Native中可以使用React语法进行移动端开发
- 使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互
2. React入门
Hello,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>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test"></div>
<div id="demo"></div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js引入 -->
<script src="../js/babel.min.js"></script>
<!-- 此处一定要写babel -->
<script type="text/babel">
// 1. 创建虚拟DOM
// 这个虚拟DOM就是一个一般对象object
const VDOM= <h1 id="title">Hello,React</h1> /*此处一定不要写引号,因为不是字符串*/
const TDOM=document.getElementById('demo')
// 2. 渲染虚拟DOM到页面
// ReactDOM.render(虚拟DOM,容器)
ReactDOM.render(VDOM,document.getElementById('test'))
// console.log('虚拟DOM',VDOM);
// console.log('真实DOM',TDOM);
console.log(typeof VDOM); //object
console.log(typeof TDOM); //object
//1. 关于虚拟DOM本质是一个一般对象
// 2. 虚拟DOM比较轻,真实DOM比较重,因为虚拟DOM是react内部在用,无需真实DOM上那么多的属性
// 3. 虚拟DOM最终会被React转化为真实DOM,呈现在页面上
</script>
</body>
</html>
jsx
jsx全称JavaScript XML,是React定义的一种类似于XML的js扩展语法:js+XML
jsx核心规则
- 定义虚拟DOM时,不要写引号
- 标签中混入js表达式时要用{}
- 样式的类名指定不要用class,要用className
- 内联样式要用style={{key1:'value1',key2:'value2'}}的形式去写
- 只有一个根标签
- 标签必须闭合
- 标签首字母: (1) 如果小写字母开头,则将该标签转为HTML中同名元素,若HTML中无同名元素,则报错 (2) 如果大写字母开头,react就去渲染对应的组件,如果组件没有定义,则报错
jsx代码演示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.title {
background-color: yellow;
width: 200px;
}
</style>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test">
</div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js引入 -->
<script src="../js/babel.min.js"></script>
<!-- 此处一定要写babel -->
<script type="text/babel">
const myId='SpongebobSquarePants'
const myData='Hello,React'
// 1. 创建虚拟DOM
const VDOM= (
<div>
<h2 id={myId} className="title">
<span style={{color:'pink',fontSize:'30px'}}>{myData}</span>
</h2>
<input type="text"></input>
</div>
)
// 2. 渲染虚拟DOM到页面
// ReactDOM.render(虚拟DOM,容器)
ReactDOM.render(VDOM,document.getElementById('test'))
// jsx语法规则:
// 1.定义虚拟DOM时,不要写引号
// 2. 标签中混入js表达式时要用{}
// 3. 样式的类名指定不要用class,要用className
// 4. 内联样式要用style={{key1:'value1',key2:'value2'}}的形式去写
// 5. 只有一个根标签
// 6. 标签必须闭合
// 7. 标签首字母:
// (1) 如果小写字母开头,则将该标签转为HTML中同名元素,若HTML中无同名元素,则报错
// (2)如果大写字母开头,react就去渲染对应的组件,如果组件没有定义,则报错
</script>
</body>
</html>
一个注意点: 虚拟DOM内不可以写语句(代码),只可以写表达式
补充知识点:语句与表达式
-
表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方。例如: (1) a (2) a+b (3) demo(1) (4) arr.map() (5) function test (){}
-
语句(代码),例如: (1) if(){} (2) for(){} (3) switch(){case:xxx}
可以简单认为,可以返回一个值的都是表达式;而语句(代码)控制代码的走向
模块与组件,模块化与组件化
模块
向外提供特定功能的js程序就是一个模块,一般就是一个js文件
为什么要拆成模块?
因为随着业务逻辑增加,代码越来越多且复杂 将js拆成一个个模块,可以复用js,简化js编写,提高js运行效率
组件
用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
使用组件可以复用代码,简化项目代码,提高运行效率
模块化
当应用的js都以模块来编写,这个应用就是模块化的应用
组件化
当应用是以多组件的方式实现,这个应用就是一个组件化的应用
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>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test">
</div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js引入 -->
<script src="../js/babel.min.js"></script>
<!-- 此处一定要写babel -->
<script type="text/babel">
// 1. 创建函数式组件
function Demo(){
console.log(this); //此处的this是undefined,是因为babel编译后开启了严格模式
return <h2>我是用函数定义的组件(适用于简单组件的定义)</h2>
}
// 2. 渲染组件到页面
// ReactDOM.render(虚拟DOM,容器)
ReactDOM.render(<Demo/>,document.getElementById('test'))
/*执行了 ReactDOM.render(<Demo/>,document.getElementById('test')) 之后发生了什么?
1. react解析组件标签,找到了Demo组件
2. 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转换为真实DOM,呈现在页面中
*/
</script>
</body>
</html>
类式组件
类相关知识复习
总结:
- 类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加属性时,才写
- 如果A类继承了B类,且A类中写了构造器,那么A类构造器中
super是必须要调用的 - 类中定义的方法,都是放在了类的原型对象上,供实例使用
<!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 type="text/javascript">
// 创建一个Person
class Person {
// 构造器方法
constructor(name, age) {
// 构造器中的this指的是谁?————类的实例对象
this.name = name
this.age = age
}
// 一般方法
// speak方法放在了哪里?————类的原型对象上,供实例使用
// 通过Person实例调用speak时,speak中的this就是Person实例
speak() {
console.log(`我叫${this.name},今年${this.age}岁`)
}
}
// 创建一个person的实例对象
// const p1 = new Person('tom', 18)
// console.log(p1);
// p1.speak()
// 创建一个student类,继承于Person类
class Student extends Person {
constructor(name, age, grade) {
super(name, age)
this.grade = grade
}
// 重写从父类继承过来的方法
speak() {
console.log(`My name is ${this.name},I am ${this.age} years old,and I am a grade${this.grade}student`);
}
study(){
console.log('I study hard');
}
}
const s1 = new Student('student', 3, '高一')
console.log(s1);
s1.speak()
s1.study()
</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>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test">
</div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js引入 -->
<script src="../js/babel.min.js"></script>
<!-- 此处一定要写babel -->
<script type="text/babel">
// 创建类式组件
class MyComponent extends React.Component{
// render是放在哪里的?——MyComponent的原型对象上,供实例使用
render(){
// render中的this是谁?——MyComponent的实例对象<=>Mycomponent组件实例对象
console.log(this);
return <h2>我是用类定义的组件(适用于复杂组件的定义)</h2>
}
}
// 2. 渲染组件到页面
// ReactDOM.render(虚拟DOM,容器)
ReactDOM.render(<MyComponent></MyComponent>,document.getElementById('test'))
// 执行了 ReactDOM.render(<MyComponent></MyComponent>,document.getElementById('test'))之后发生了什么?
// 1. React解析组件标签,找到了MyComponent组件
// 2. 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上render方法
// 3. 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中
</script>
</body>
</html>
state
- 函数式组件:是用函数定义的组件,适用于简单组件的定义
- 类式组件,是用类定义的组件,适用于复杂组件的定义
这里的简单组件和复杂组件的区分标准是看其有无state
state是组件实例的三大核心属性之一
也就是说只有类式组件有state这个属性,函数式组件没有实例的概念,也就没有state这个属性
复习:原生js绑定事件
原生JavaScript绑定事件的三种方式:
<!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>
<button id="btn1">button1</button>
<button id="btn2">button2</button>
<button id="btn3" onclick="demo()">button3</button>
<script>
const btn1=document.getElementById('btn1')
btn1.addEventListener('click',()=>{
alert('按钮1被点击了')
})
const btn2=document.getElementById('btn2')
btn2.onclick=()=>{
alert('按钮2被点击了')
}
function demo(){
alert('按钮3被点击了')
}
</script>
</body>
</html>
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>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test">
</div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js引入 -->
<script src="../js/babel.min.js"></script>
<!-- 此处一定要写babel -->
<script type="text/babel">
// 创建类式组件
class Weather extends React.Component{
constructor(props){
super(props)
this.state={isHot:true}
}
// render是放在哪里的?——MyComponent的原型对象上,供实例使用
render(){
// render中的this是谁?——MyComponent的实例对象<=>Mycomponent组件实例对象
const {isHot} =this.state
// React里面如何绑定事件? onclick=>onClick ; demo()=>demo
return <h2 onClick={demo}>今天天气很{isHot?'炎热':'凉爽'}</h2>
}
}
// 2. 渲染组件到页面
// ReactDOM.render(虚拟DOM,容器)
ReactDOM.render(<Weather></Weather>,document.getElementById('test'))
// const title=document.getElementById('title')
// title.onclick=()=>{
// console.log('我被点击了');
// }
function demo(){
console.log('我被点击了');
}
</script>
</body>
</html>
初识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>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test">
</div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js引入 -->
<script src="../js/babel.min.js"></script>
<!-- 此处一定要写babel -->
<script type="text/babel">
// 创建类式组件
class Weather extends React.Component{
// 构造器调用几次?——一次
constructor(props){
super(props)
// 初始化状态
this.state={isHot:true,wind:'微风'}
// 解决changeWeather中this指向问题
this.changeWeather=this.changeWeather.bind(this)
}
// render是放在哪里的?——MyComponent的原型对象上,供实例使用
// render调用几次?——1+n次 1次是初始化的那次,n是状态更新的次数
render(){
console.log('render');
// render中的this是谁?——MyComponent的实例对象<=>Mycomponent组件实例对象
const {isHot,wind} =this.state
// React里面如何绑定事件? onclick=>onClick ; demo()=>demo
return <h2 onClick={this.changeWeather}>今天天气很{isHot?'炎热':'凉爽'},{wind}</h2>
}
// changeWeather调用几次?——点几次调几次
changeWeather(){
// console.log('我被点击了');
// changeWeather放在哪里了?——Weather的原型对象上,供实例使用
// 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
// 类中的方法默认开启了局部的严格模式,所以changeWeather中的this是undefined
// console.log(this.state.isHot);
// console.log(this);
// 获取原来的isHot值
const isHot=this.state.isHot
// 严重注意:状态必须通过setState进行修改,且更新是一种合并,不是替换
this.setState({isHot:!isHot})
// 严重注意:状态(state)不可直接更改,要借助一个内置的API去更改
// 下面就是直接更改:
// this.state.isHot=!isHot
}
}
// 2. 渲染组件到页面
// ReactDOM.render(虚拟DOM,容器)
ReactDOM.render(<Weather></Weather>,document.getElementById('test'))
// const title=document.getElementById('title')
// title.onclick=()=>{
// console.log('我被点击了');
// }
</script>
</body>
</html>
精简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>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test">
</div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js引入 -->
<script src="../js/babel.min.js"></script>
<!-- 此处一定要写babel -->
<script type="text/babel">
class Weather extends React.Component{
// 初始化状态
state={isHot:true,wind:'微风'}
render(){
const {isHot,wind}=this.state
return <h2 onClick={this.changeWeather}>今天天气很{isHot?'炎热':'凉爽'},{wind}</h2>
}
// 自定义方法——要用赋值语句的形式+箭头函数
changeWeather=()=>{
const isHot=this.state.isHot
this.setState({isHot:!isHot})
}
}
ReactDOM.render(<Weather></Weather>,document.getElementById('test'))
</script>
</body>
</html>
总结
- state是组件对象的最重要的属性,值是对象(可以包含多个Key-value的组合)
- 组件被称为状态机,通过更新组件的state来更新对应的页面显示(重新渲染组件)
注意:
- 组件中render方法中的this为组件实例对象
- 组件自定义方法中this为undefined,如何解决?
- 强制绑定this:通过函数对象的bind()
- 箭头函数
- 状态数据,不能直接修改或者更新
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>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test">
</div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js引入 -->
<script src="../js/babel.min.js"></script>
<!-- 此处一定要写babel -->
<script type="text/babel">
// 创建组件
class Person extends React.Component {
render(){
console.log(this);
const {name,age,sex}=this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
// 渲染组件
ReactDOM.render(<Person name="tom" age="18" sex="female"></Person>,document.getElementById('test'))
</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>
</head>
<body>
<script>
let arr1 = [1, 2, 3, 4, 5]
let arr2 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 展开一个数组
// console.log(...arr1);
// 连接数组
let arr3 = [...arr1, ...arr2]
// console.log(arr3);
// 函数传参
function sum(...numbers) {
// return a+b
// console.log(numbers);
return numbers.reduce((preValue, currentValue) => {
return preValue + currentValue
})
}
// console.log(sum(1, 1, 3, 3, 12, 412, 5, 3, 56));
// 构造字面量对象时使用展开语法
let person = {
name: 'John',
age: 123,
firend: {
firend1: {
name: "f1",
age: 3
},
friend2: {
name: "f2",
age: 4
}
}
}
// console.log(...person); //报错,展开运算符不能展开对象
let person2 = {
...person
} //可以展开
console.log(person2);
// 合并
let person3 = {
...person,
name: "Name",
tel: 13888888888
}
// console.log(person3);
</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>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test">
</div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js引入 -->
<script src="../js/babel.min.js"></script>
<!-- 此处一定要写babel -->
<script type="text/babel">
// 创建组件
class Person extends React.Component {
render(){
console.log(this);
const {name,age,sex}=this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
// 渲染组件
const p={name:'Lily',age:87,sex:'male'}
ReactDOM.render(<Person {...p}></Person>,document.getElementById('test'))
</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>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test">
</div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js引入 -->
<script src="../js/babel.min.js"></script>
<!-- 引入 prop-types,用于对组件标签属性进行限制 -->
<script src="../js/prop-types.js"></script>
<!-- 此处一定要写babel -->
<script type="text/babel">
// 创建组件
class Person extends React.Component {
// 对标签属性进行类型和必要性的限制
static propTypes={
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
speak:PropTypes.func,
}
// 指定默认的标签属性值
static defaultProps={
sex:'不男不女',
age:1
}
render(){
console.log(this);
const {name,age,sex}=this.props
// props是只读的
// this.props.name="readonlytest" //报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
// 渲染组件
const p={name:'haha',age:87,sex:'male'}
ReactDOM.render(<Person {...p} speak={speak}></Person>,document.getElementById('test'))
function speak(){
console.log('speaking...');
}
</script>
</body>
</html>
关于在类式组件中是否要写constructor,是否在constructor中传入props的问题: 构造器是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问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>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test">
</div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js引入 -->
<script src="../js/babel.min.js"></script>
<script src="../js/prop-types.js"></script>
<!-- 此处一定要写babel -->
<script type="text/babel">
function Person(props){
const {name,age,sex}=props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
Person.prototype={
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
}
Person.defaultProps={
sex:'male',
age:18
}
// 2. 渲染虚拟DOM到页面
// ReactDOM.render(虚拟DOM,容器)
ReactDOM.render(<Person name='name' ></Person>,document.getElementById('test'))
</script>
</body>
</html>
props理解
- 每个组件对象都会有props属性
- 组件标签的所有属性都保存在props中
- 通过标签属性从组件外向组件内传递变化的数据
- 组件内部不要修改props数据
refs
字符串形式的回调函数
<!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>
<!-- 准备好一个容器 -->
<div id="test">
</div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js引入 -->
<script src="../js/babel.min.js"></script>
<!-- 此处一定要写babel -->
<script type="text/babel">
class Demo extends React.Component{
showData=()=>{
// console.log(this.refs.input1);
const {input1}=this.refs
alert(input1.value)
}
showData2=()=>{
const {input2}=this.refs
alert(input2.value)
}
render(){
return(
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
ReactDOM.render(<Demo />,document.getElementById('test'))
</script>
</body>
<html>
回调函数形式的ref
<!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>
<!-- 准备好一个容器 -->
<div id="test">
</div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js引入 -->
<script src="../js/babel.min.js"></script>
<!-- 此处一定要写babel -->
<script type="text/babel">
class Demo extends React.Component{
showData=()=>{
// console.log(this.refs.input1);
const {input1}=this
alert(input1.value)
}
showData2=()=>{
const {input2}=this
alert(input2.value)
}
render(){
return(
<div>
<input ref={(currentNode)=>{this.input1=currentNode}} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref={c=>this.input2=c} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
ReactDOM.render(<Demo />,document.getElementById('test'))
</script>
</body>
</html>
createRef
<!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>
<!-- 准备好一个容器 -->
<div id="test">
</div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js引入 -->
<script src="../js/babel.min.js"></script>
<!-- 此处一定要写babel -->
<script type="text/babel">
class Demo extends React.Component{
// React.createRef() 调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是专人专用的
myRef=React.createRef()
showData=()=>{
// console.log(this.refs.input1);
// const {input1}=this
// alert(input1.value)
// console.log(this.myRef.current.value);
alert(this.myRef.current.value)
}
showData2=()=>{
const {input2}=this
alert(input2.value)
}
render(){
return(
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref={c=>this.input2=c} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
ReactDOM.render(<Demo />,document.getElementById('test'))
</script>
</body>
</html>
ref总结
- 在条件允许的情况下,尽量避免字符串形式的ref
- 内联形式的函数是在实际开发中比较常用的
- createRef是目前React最推荐的一种创建ref的形式
React中的事件处理
- 通过
onXxx属性指定事件处理函数,注意大小写- React中使用的是自定义(合成)事件,而不是使用的原生DOM事件,这样做是为了更好的兼容性
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素),这样做是为了高效
- 可以通过
event.target得到发生事件的DOM元素对象,这样可以避免过渡的使用ref
React中收集表单数据
非受控组件
所有输入类DOM的值现用现取的组件是非受控组件
受控组件
所有输入类DOM的值随着输入就把输入的内容维护到状态(state)中,等需要用时从状态(state)中取就是受控组件
高阶函数与函数柯里化
高阶函数:如果一个函数符合下面两个规范中的任何一个,那该函数就是高阶函数:
1. 如果A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
2. 若A函数,调用的返回值是一个函数,那么A就可以称之为高阶函数
常见的高阶函数:
-
Promise
-
setTimeout
-
arr.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>
</head>
<body>
<script>
function sum(a){
return (b)=>{
return (c)=>{
return a+b+c
}
}
}
const res=sum(1)(2)(3)
console.log(res);
</script>
</body>
</html>
组件的生命周期
生命周期回调函数<=>生命周期钩子函数<=>生命周期函数<=>生命周期钩子
理解:
- 组件从创建到死亡它会经历一些特定的阶段
- React组件中包含一系列钩子函数,会在特定的时刻调用
- 我们在定义组件时,会在特定的生命周期回调函数中做特定的工作
生命周期钩子
.png)
<!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>
<!-- 准备好一个容器 -->
<div id="test">
</div>
<!-- 引入react核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js引入 -->
<script src="../js/babel.min.js"></script>
<!-- 此处一定要写babel -->
<script type="text/babel">
class Count extends React.Component{
constructor(props){
console.log('Count-constructor');
super(props)
// 初始化状态
this.state={count:0}
}
add=()=>{
// 获取原来状态
const {count}=this.state
// 更新状态
this.setState({count:count+1})
}
// 卸载组件按钮的回调
death=()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 强制更新按钮的回调
force=()=>{
this.forceUpdate()
}
// 组件将要挂载的钩子
componentWillMount(){
console.log('Count-componentWillMount');
}
// 组件挂载完毕的钩子
componentDidMount(){
console.log('Count-componentDidMount');
}
// 组件将要卸载的钩子
componentWillUnmount(){
console.log('Count-componentWillUnmount');
}
// 控制组件更新的阀门
shouldComponentUpdate(){
console.log('Count-shouldComponentUpdate');
return false
}
// 组件将要更新的钩子
componentWillUpdate(){
console.log('Count-componentWillUpdate');
}
// 组件更新完毕的钩子
componentDidUpdate(){
console.log('Count-componentDidUpdate');
}
render(){
console.log('Count-render');
const {count}=this.state
return(
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我加1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
// 父组件A
class A extends React.Component{
state={carName:'benz'}
changeCar=()=>{
this.setState({carName:'BMW'})
}
render(){
return (
<div>
<div>A</div>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName}></B>
</div>
)
}
}
// 子组件B
class B extends React.Component{
// 组件将要接收新的props的钩子
componentWillReceiveProps(props){
console.log('B-componentWillReceiveProps',props);
}
// 控制组件更新的阀门
shouldComponentUpdate(){
return true
console.log('B-shouldComponentUpdate');
}
// 组件将要更新的钩子
componentWillUpdate(){
console.log('B-componentWillUpdate');
}
// 组件更新完毕的钩子
componentDidUpdate(){
console.log('B-componentDidUpdate');
}
render(){
console.log('B-render');
return(
<div>我是B组件,接收到的车是{this.props.carName}</div>
)
}
}
// ReactDOM.render(<Count ></Count>,document.getElementById('test'))
ReactDOM.render(<A ></A>,document.getElementById('test'))
</script>
</body>
</html>
生命周期(旧)分为三个阶段:
- 初始化阶段:有
ReactDOM.render()触发——初次渲染- constructor()
- componentWillMount()
- rnder()
- componentDidMount()——常用,一般在这里做一些初始化的事,例如:开启定时器,发送网络请求、订阅消息等
- 更新阶段:由组件内部
this.setState()或者父组件重新render触发- shouldConponentUpdate()
- componentWillUpdate()
- render()
- componentDidUpdate()
- 卸载组件:由ReactDOM.unmountComponentAtNode触发
- componentWillUnmount()——常用:一般做一些收尾的事,例如:关闭定时器、取消订阅
生命周期(新)的三个阶段:
- 初始化阶段:由ReactDOM.render()触发——初次渲染
- constructor()
- getDerivedStateFromProps
- render()
- componentDidMount()
- 更新阶段:由组件内部this.setState()或父组件重新render触发
- getDerivedStateFromProps
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
- 卸载组件:由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()
三个重要的钩子:
- render:初始化渲染或者更新渲染调用
- componentDidMount:开启监听,发送Ajax请求
- componentWillUnmount:做一些收尾工作,如:清理定时器
即将废弃的钩子:
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
DOM 中Diff算法
key的作用
经典面试题:
- react/vue中的key有什么作用(key的内部原理是什么)
- 为什么遍历列表时,key最好不要用index?
虚拟DOM中key的作用: 简单的说:key是虚拟DOM 对象的标识,在更新显示时key起着极其重要的作用 详细的说:当状态中的数据发生变化时,react会根据新数据生成新的虚拟DOM 随后React进行新虚拟DOM与旧虚拟DOM的Diff比较,比较规则如下:
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 若虚拟DOM中内容没变,直接使用之前的真实DOM
- 若虚拟DOM中内容变了,则生成新的真实的DOM,随后替换掉页面中之前的真实DOM
- 旧虚拟DOM中未找到与新虚拟DOM相同的key
- 根据数据创建新的真实DOM,随后渲染到页面
用index作为key可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新,从而导致效率变低
- 如果结构中还包含输入类的DOM: 会产生错误DOM更新,界面会产生问题
- 如果不存在对数据的逆序添加、逆序删除等操作,仅用于渲染列表用户展示,使用index作为key 是没有问题的
开发中如何选择key?
- 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值
- 如果确定只是简单的展示数据,用index作为key也是可以的
React应用(基于脚手架)
- React提供了一个用于创建react项目的脚手架库:
create-react-app - 项目的整体技术架构为:react+webpack+es6+eslint
- 使用脚手架开发的项目的特点:模块化,组件化,工程化
创建项目并启动
- 全局安装
npm i -g create-react-app - 切换到想创建项目的目录,使用命令:
create-react-app project_name - 进入项目文件夹:
cd project_name - 启动项目:
npm start
备注:在2022年4月15日尝试使用create-react-app project_name发现react已经不支持使用这种方式创建一个react新项目:
We no longer support global installation of Create React App.
Please remove any global installs with one of the following commands:
- npm uninstall -g create-react-app
- yarn global remove create-react-app
所以将create-react-app卸载,使用npm init react-app my-app创建新项目
脚手架文件介绍
index.html
%PUBLIC_URL%代表public文件夹的路径
用于配置浏览器页签+地址栏的颜色(仅支持安卓手机浏览器):
<meta name="theme-color" content="red" />
用于指定网页添加到手机主屏幕后的图标(仅支持苹果手机):
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png">
应用加壳时的配置文件:
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
浏览器不支持js则展示标签中的内容:
<noscript>...</noscript>
robots.txt
爬虫相关协议规则文件
TodoList案例
总结:
- 动态初始化列表,如何确定将数据放在那个组件的state中?
- 某个组件使用:放在自身的state中
- 某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)
- 父子之间通信:
- 父组件给子组件传递数据:通过props传递
- 子组件给父组件传递数据:通过props传递,要求父组件宪哥子组件传递一个函数
细节:
defaultChecked只在第一次的时候有效果,之后不起作用,类似的还有defaultValue和value- checked单独用的话会报警告,而且相当于写死了,一般和onChange配合使用
- 状态在哪里,操作状态的方法就在哪里
React ajax
前言
前置说明
- React本身只关注于界面,并不包含发送ajax请求的代码(React不具备ajax功能)
- 前端应用需要通过ajax请求与后台进行交互(JSON数据)(确实有这个需求)
- react应用中需要集成第三方ajax库或者自己封装
React配置代理总结
方法一
在package.json中追加如下配置
"proxy":"http://localhost:5000"
说明:
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
方法二
-
第一步:创建代理配置文件
在src下创建配置文件:src/setupProxy.js -
编写setupProxy.js配置具体代理规则:
const proxy = require('http-proxy-middleware') module.exports = function(app) { app.use( proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000) target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址) changeOrigin: true, //控制服务器接收到的请求头中host字段的值 /* changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000 changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000 changeOrigin默认值为false,但我们一般将changeOrigin值设为true */ pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置) }), proxy('/api2', { target: 'http://localhost:5001', changeOrigin: true, pathRewrite: {'^/api2': ''} }) ) }
说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀。
消息订阅-发布机制
- 先订阅,再发布
- 适用于任意组件间通信
- 要在组件的componentWillUnmount中取消订阅
fetch发送请求
try {
const response = await fetch(`/api1/search/users?q=${keyword}`)
const data = await response.json()
console.log(response.data);
PubSub.publish('peiqi', { isLoading: false, users: data.items })
// console.log(data);
} catch (error) {
console.log('fail', error);
PubSub.publish('peiqi', { isLoading: false, err: error.message })
}
React路由
路由的理解
什么是路由
- 路由是一个映射关系(key:value)
- key为路径,value可能是function或者component
路由分类
- 后端路由
- 理解:value是function,用来处理客户端提交的请求
- 注册路由:router.get(path,function(req,res))
- 工作过程:当node接到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据
- 前端路由
- 浏览器端路由,value是
component,用于展示页面内容 - 注册路由:
- 工作过程:当浏览器的
path变为/test时,当前路由组件就会变为Test组件
- 浏览器端路由,value是
路由的基本使用
- 明确好界面中的导航区、展示区
- 导航区的a标签改为Link标签
<Link to="/xxx">Demo</Link> - 展示区写Route标签进行路径的匹配
<Route path="/xxx" component={demo}> <App>的最外侧包裹了一个<BrowserRouter>或者<HashRouter>
路由组件与一般组件
路由组件和一般组件的区别:
- 写法不同:
- 一般组件:
<Demo> - 路由组件:
<Route path="/demo" component={Demo}>
- 一般组件:
- 存放位置不同:
- 一般组件放在components文件夹中
- 路由组件放在pages文件夹中
- 接收到的props不同:
- 一般组件:写组件标签时传递了什么,就能收到什么
- 路由组件:接收到三个固定的属性:
- history
- location
- match
NavLink与封装NavLink
封装的<MyNavLink></MyNavLink>是一个一般组件
- NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
- 标签体内容是一个特殊的标签属性
- 通过
this.props.children可以获取标签体内容
Switch的使用
- 通常情况下path和component是一一对应的关系
- Switch可以提高路由匹配效率
解决多级路径刷新页面样式丢失的问题
public/index.html中引入样式时不写./写/(常用)public/index.html中引入样式时不写./写%PUBLIC_URL%(常用)- 使用HashRouter
路由的严格匹配和模糊匹配
- 默认使用的是模糊匹配(简单记:输入的路径必须要包含匹配的路径,且顺序要一致)
- 开启严格匹配:
<Route exact={true} path="/about" component={About}> - 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
Redirect的使用
- 一般在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
<Switch>
<Route path='/about' component={About}></Route>
<Route path='/home' component={Home}></Route>
<Redirect to="/about"></Redirect>
</Switch>
嵌套路由
向路由组件传递参数
params参数
- 路由链接(携带参数):
<Link to="/demo/test/tom/18">详情</Link> - 注册路由(声明接收):
<Route path="/demo/test/:name/:age" component={Test} /> - 接收参数:
const {name,age}=this.props.match.params
search参数
- 路由链接(携带参数):
<Link to="/demo/test?name=tom&age=18">详情</Link> - 注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test} /> - 接收参数:
const {name,age}=this.props.location.search备注:获取到的search是urlencoded编码字符串,需要借助query-string解析import qs form "qurey-string"
state参数
- 路由链接(携带参数):
<Link to={{path:"/demo/test",state:{name:'tom',age:18}}}>详情</Link> - 注册路由(无需声明,正常注册即可):
<Route path="/demo/test" component={Test} /> - 接收参数:
const {name,age}=this.props.location.state备注:刷新也可以保留住参数
push 与replace
- 默认是push模式
- 修改为replace模式可以在Link或者NavLink等跳转组件中加入 :
replace={true}或者直接简写:replace
编程式路由导航
借助this.props.history对象上的API对路由操作跳转、前进后退
this.props.history.push()
this.props.history.replace()
this.props.history.goBack()
this.props.history.goForward()
this.props.history.go()
BrowserRouter与HashRouter的区别
- 底层原理不一样:
- BrowserRouter使用的是H5的history API,不兼容IE9及以下版本
- HashRouter使用的是URL的哈希值
- path表现形式不一样
- BrowserRouter中路径中没有
#,例如:localhost:3000/demo/test - HashRouter的路径中包含
#例如:localhost:3000/#/demo/test
- BrowserRouter中路径中没有
- 刷新后对路由state参数的影响
- BrowserRouter没有任何影响,因为state保存在history对象中
- HashRouter刷新后会导致路由state参数的丢失
- HashRouter可以用于解决一些路径错误相关的问题
React UI组件库
ant-design
redux
- redux是一个专门用于做状态管理的js库(不是react插件库)
- 它可以用在react ,angular,vue等项目中,但基本与react配合使用
- 作用:集中式管理react应用中多个组件共享的状态
什么情况下需要使用redux?
- 某个组件的状态,需要让其他组件可以随时拿到(共享)
- 一个组件需要改变另一个组件的状态(通信)
- 总体原则:能不用就不用,如果不用比较吃力才考虑使用
redux的三个核心概念
action
- action是动作的对象
- 包含两个属性:
- type:标识属性,值为字符串,唯一,必要属性
- data:数据类型,值类型任意,可选属性
- 例如:
{type:'ADD_STUDENT',data:{name:'tom',age:18}}
reducer
- 用于初始化状态,加工状态
- 加工时,根据旧的state和action,产生的state的纯函数
store
- 将state,action,reducer联系在一起的对象
redux求和案例
精简版
- 去除Count组件自身的状态state
- src文件件下建立:
- redux
- store.js
- count_render.js
- store.js:
- 引入redux中的createStore函数,创建一个store
- createStore调用时要传入一个为其服务的reducer
- 暴露store对象
- count_reducer.js
- reducer本质是一个函数,接收prestate,action,返回加工后的状态
- reducer有两个作用:初始化状态、加工状态
- reducer被第一次调用时,是state自动触发的,传递的preState是undefined
- 在index.js中检测store中状态的改变,一旦发生改变重新渲染
<App/>备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写
完整版
新增文件:
- count_action.js专门用于创建action对象
- constant.js放置容易写错的type值
redux异步action版
明确:延迟的动作不想交给组件自身,想交给action 何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回 具体编码:
npm i redux-thunk,并配置在store中- 创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务
- 异步任务有结果后,分发一个同步任务的action去真正操作数据
备注:异步action不是必须要写的,完全可以自己等待异步任务的结果再去分发同步action
react-redux的基本使用
明确两个概念:
- UI组件:不能使用任何redux的API,只负责页面的呈现、交互等
- 容器组件:负责和redux通信,将结果交给UI组件
如何创建一个容器组件——靠react-redux的connect函数
connect(mapStateToProps,mapDispatchToProps)(ui组件)
其中:
- mapStateToProps:映射状态,返回值是一个对象
- mapDispatchToProps:映射操作状态的方法,返回值是一个对象
备注:
- 容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
- mapDispatchToProps也可以是一个对象
react-redux数据共享版
定义一个Person组件,和Count组件通过redux共享数据 为Person组件编写:reducer,action,配置constant常量 重点:Person的reducer和Count的Reducer要使用combineReducers进行合并,合并后的总状态是一个对象 交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”
开发者工具的使用
安装:
npm i redux-devtools-extension
在store中配置:
import {composeWithDevTools} from 'redux-devtools-extension'
const store =createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
高阶函数和纯函数
纯函数
一类特别的函数:只要是同样的输入(实参),必定得到同样的输出(返回)
纯函数必须遵守以下约束:
- 不得改写参数数据
- 不会产生任何副作用,例如网络请求,输入和输出设备
- 不能调用
Date.now()或者Math.random()等不纯的方法
redux的reducer函数必须是一个纯函数
React 扩展
setState
setState更新状态的两种写法:
对象式setState
setState(stateChange,[callback])
- stateChange为状态改变对象(该对象可以体现出状态的更改)
- callback是可选的回调函数,它在状态更新完毕,界面也更新后(render调用后)才被调用
函数式setState
setState(updater,[callback])
- updater为返回stateChange对象的函数
- updater可以接收到state和props
- callback是可选的回调函数,它在状态更新、界面也更新后(render调用后)才被调用
总结
使用原则:
- 如果新状态不依赖原状态,使用对象式
- 如果新状态依赖原状态,使用函数式
lazyLoad
Hooks
Hook是React16.8版本增加的新特性,新语法,可以让你在函数组件中使用state以及其他的React特性
三个常用的hook:
- State hook:
React.useState() - Effect hook:
React.useEffect() - Ref Hook:
React.useRef()
state Hook
state Hook让函数组件也可以有state状态,并进行状态数据的读写操作
语法:
const [xxx,setXxx]=React.useState(initValue)
useState说明:
- 参数:第一次初始化指定的值在内部换成
- 返回值:包含两个元素的数组,第一个为内部当前状态值,内部用其覆盖原来的状态值
setXxx()的两种写法: 第一种:
setXxx(newValue)
// 参数为非函数值,直接指定新的状态值,内部用其覆盖原来的值
第二种:
setXxx(value=>newValue)
参数为函数,接收原来的状态值,返回新的状态值,内部用其覆盖原来的状态值
effect Hook
Effect Hook可以让你在函数组件中执行副作用操作(用于模拟类组件中生命周期钩子)
React中的副作用操作:
- 发送Ajax请求数据获取
- 设置订阅
- 起动定时器
- 手动更改真是DOM
语法说明:
useEffect(()=>{
// 在此执行任何副作用操作
return()=>{
// 在组件卸载前执行
// 在此做一些收尾工作,比如清除定时器/取消订阅等
}
},[stateValue]) //如果指定的是[],回调函数只会在第一次render后执行
可以把useEffect Hook可以看做三个钩子的组合:
- componentDidMount()
- componentDidUpdata()
- componentWillUnmount()
Ref Hook
Ref Hook可以在函数组件中存储、查找组件内标签或者任意其他数据
语法:
const refContainer=useRef()
作用: 保存标签对象,功能和React.createRef()一样
Fragment
在jsx文件中如果有多个标签必须在这些标签外套一层div,这是jsx的规则
现在可以使用<Fragment></Fragment>来代替div了
jsx在编译成js的时候就会自动把Fragment去掉,页面中也不会出现div结构了
其实使用空标签<></>也能实现类似的效果
空标签和Fragment标签还是有一些区别的:
- Fragment标签可以加一个key属性,空标签是不能加任何属性的
- Fragment也只能加key属性,别的属性也加不了
content
一种组件间通信的方式(常用于组件件和后代组件间通信)
使用:
- 创建Context容器对象:
const XxxContext=React.createContext()
- 渲染子组件时,外面包裹xxxContext.Provider,通过value属性给后代传递数据
<xxxContext.Provider value={数据}>
子组件
</xxxContext.Provider>
- 后代组件读取数据:
// 第一种方式
static contextType=xxxConext
this.context
// 第二种方式
<xxxContext.Consumer>
{
value=>(
要显示的内容
)
}
</xxxContext.Consumer>
组件优化
Component的两个问题:
- 只要执行setState,即使不改变状态数据,组件也会重新render
- 只当前组件重新render,就会自动重新render子组件,纵使子组件没有用到父组件的任何数据,效率低
问题的原因:
Component中的
shouldComponentUpdate()总是返回TRUE
期望中效率高的做法: 只有当组件中的state或者props数据发生变化时才重新render
解决两个问题的方法:
- 重写
shouldComponentUpdate方法 比较新旧state或者props数据,如果有变化才返回TRUE,没有返回FALSE - 使用PureComponent
PureComponent重写了
shouldComponentUpdate(),只有state或者props数据有变化才能返回TRUE 注意: 只是进行state和props数据的浅计较,如果只是数据对象内部数据变了,返回false 不要直接修改state数据,而是要产生新数据
项目中一般使用PureComponent来优化
render props
如何向组件内部动态的传入带有内容的结构?
- Vue: 使用slot技术,也就是通过组件标签传入结构
- React: 使用children props:通过组件标签传入结构 使用render props:通过组件标签属性传入结构,一般用render函数属性
child props
<A>
<B>xxx</B>
</A>
{this.props.children}
问题:如果B组件需要A组件内的数据,做不到
render props
<A render={(data)=><C data={data}/>}>
// A组件
{this.props.render(内部state数据)}
// C组件:
读取A组价传入的数据显示{this.props.data}
错误边界
错误边界:用来捕获后代组件错误,渲染处备用页面
特点: 只能捕获组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
getDerivedStateFromError配合componentDidCatch
static getDerivedStateFromError(error){
console.log(error);
// 在render之前触发,返回新的state
return {
hasError:true
}
}
componentDidCatch(error,info){
// 统计错误页面,发送请求发送到后台去
console.log(err,info)
}
组件间通信方式的总结
组件间的关系:
- 父子关系
- 兄弟关系
- 祖孙关系
几种通信方式
props
- children props
- render props
消息订阅-发布
pub-sub,event等等
集中式管理
redux、dva等
conText:
生产者-消费者模式
比较好的搭配方式
- 父子组件:props
- 兄弟组件:消息订阅-发布,集中式管理
- 祖孙组件:消息订阅-发布,集中式管理,conText
React Router 6
概述
React Router以三个不同的包发布到npm 上,他们分别是:
- react-router:路由的核心库,提供了很多的组件,钩子
- react-router-dom:包含react-router所有内容,并添加一些专门用于DOM的组件,例如:
<BrowserRouter>等 - react-router-native:包括react-router所有内容,并添加一些专门用于ReactNative的API,例如:
<NativeRouter>等
<BrowserRouter>
<BrowserRouter>用于包裹整个应用
代码:
import React from 'react'
import ReactDOM from 'react-dom'
import {BrowserRouter} from 'react-router-dom'
ReactDOM.render(
<BrowserRouter>
{整个结构,通常为App组件}
</BrowserRouter>
)
<Routes>
- V6版本使用
<Routes>代替原先的<Switch> <Routes>和<Route>要配合使用,使用<Routes>包裹<Route>