目录
react入门
hello react
HTML代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello_react</title>
</head>
<body>
<!-- 1. 准备好一个“容器” -->
<div id="test"></div>
<!-- 2. 引入react核心库 注意这个引入顺序 不可变-->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel" > /* 此处一定要写babel */
// 3.创建虚拟DOM
const VDOM = <h1>Hello,React</h1> /* 此处一定不要写引号,因为不是字符串 */
// JSX可以将HTML和JS混用
// 4.渲染虚拟DOM到页面
// ReactDOM.render(虚拟DOM,容器) 你把哪个虚拟DOM渲染到哪个容器中 ReactDOM操作虚拟DOM
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
</body>
</html>
创建虚拟DOM的两种方式
为什么要用JSX:JSX可以让程序员更简单的创建虚拟DOM 假如我们要实现下面的结构
- JS创建虚拟DOM:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>2_使用js创建虚拟DOM</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" >
//1.创建虚拟DOM React.createElement(标签名,标签属性,标签内容)创建虚拟DOM document.createElement创建真实DOM
const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello,React'))
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
</body>
</html>
- JSX创建虚拟DOM:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>1_使用jsx创建虚拟DOM</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel" > /* 此处一定要写babel */
// Babel就是将你这里面的代码翻译成了原生JS的代码:
// const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello,React'))
//1.创建虚拟DOM
const VDOM = ( /* 此处一定不要写引号,因为不是字符串 */
<h1 id="title">
<span>Hello,React</span>
</h1>
)
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
</body>
</html>
- 关于虚拟DOM:
(1)本质是Object类型的对象(一般对象)
(2)虚拟DOM身上的属性比较少,真实DOM身上的属性比较多,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。
(3)虚拟DOM最终会被React转化为真实DOM,呈现在页面上。
JSX语法
- 定义虚拟DOM时,不要写引号。
- 标签中混入JS表达式时要用{}。
- 样式的类名指定不要用class,要用className。
- 内联样式,要用style={{key:value}}的形式去写。
- 只有一个根标签
- 标签必须闭合
- 标签首字母
(1).若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
一定注意区分:【js语句(代码)】与【js表达式】
- 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
下面这些都是表达式:(你拿一个变量在左侧接 能接到值 他就是表达式)
(1). a
(2). a+b
(3). demo(1)
(4). arr.map()
(5). function test () {}- 语句(代码):
下面这些都是语句(代码):(控制代码走向 没有值 不属于表达式)
(1).if(){}
(2).for(){}
(3).switch(){case:xxxx}
组件与模块
- 模块
(1)理解:向外提供特定功能的js程序, 一般就是一个js文件
(2)为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
(3)作用:复用js, 简化js的编写, 提高js运行效率 - 组件
(1)理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
(2)为什么要用组件: 一个界面的功能更复杂
(3)作用:复用编码, 简化项目编码, 提高运行效率 - 模块化:当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
- 组件化:当应用是以多组件的方式实现, 这个应用就是一个组件化的应用
React面向组件编程
定义组件的两种方式
函数式组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>1_函数式组件</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
//1.创建函数式组件 函数名必须大写 用于区分你是个组件
function MyComponent(){
// 此处的this是undefined,因为babel编译后开启了严格模式
// 严格模式下禁止自定义函数里的this指向window
console.log(this);
// 2. 函数必须有返回值。返回值就是组件内容,也就是你要展示的东西
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
// 3.渲染组件到页面 ReactDOM.render(组件的标签(必须闭合),要渲染进的容器)
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>
</body>
</html>
执行了ReactDOM.render(<MyComponent/>.......
之后,发生了什么?
- React解析组件标签,找到了MyComponent组件。
- 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
类式组件
复习类的基本知识
- 类中的构造器不是必须要写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
- 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的。
- 类中所定义的方法,都放在了类的原型对象上,供实例去使用。
class Person {//创建一个Person类
constructor(name,age){//构造器方法
//构造器中的this是谁?—— 类的实例对象
this.name = name
this.age = age
}
speak(){//一般方法
//speak方法放在了哪里?——类的原型对象上,供实例使用
//通过Person实例调用speak时,speak中的this就是Person实例
console.log(`我叫${this.name},我年龄是${this.age}`);
}
}
class Student extends Person {//创建一个Student类,继承于Person类
constructor(name,age,grade){
super(name,age)
this.grade = grade
this.school = '尚硅谷'
}
//重写从父类继承过来的方法
speak(){
console.log(`我叫${this.name},我年龄是${this.age},我读的是${this.grade}年级`);
this.study()
}
study(){
//study方法放在了哪里?——类的原型对象上,供实例使用
//通过Student实例调用study时,study中的this就是Student实例
console.log('我很努力的学习');
}
}
class Car {
constructor(name,price){
this.name = name
this.price = price
// this.wheel = 4
}
//类中可以直接写赋值语句,如下代码的含义是:给Car的实例对象添加一个属性,名为a,值为1
a = 1
wheel = 4
// 在变量名前面加static,表示这是个静态变量,静态变量是在类Car身上的,不是实例对象,是这个对象本身有的
static demo = 100
}
const c1 = new Car('奔驰c63',199)
console.log(c1);
console.log(Car.demo);
类式组件定义方式:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>2_类式组件</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
//1.创建类式组件
// 必须继承React.Component 必须定义render()函数 render()函数中必须有返回值
class MyComponent extends React.Component {
render(){
//render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
//render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
console.log('render中的this:',this);
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
//2.渲染组件到页面 这里的render和上面的render不是同一个东西 只是重名了而已
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>
</body>
</html>
执行了ReactDOM.render(<MyComponent/>.......
之后,发生了什么?
- React解析组件标签,找到了MyComponent组件。
- 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
- 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
state
- 类中的this指向问题
class Person {
constructor(name,age){
this.name = name
this.age = age
}
study(){
//study方法放在了哪里?——类的原型对象上,供实例使用
//通过Person实例调用study时,study中的this就是Person实例
console.log(this);
}
}
const p1 = new Person('tom',18)
p1.study() //通过实例调用study方法
const x = p1.study
x()// 类中你定义的所有方法都自动开启了严格模式(局部) 所以这里打印undefined
- state
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>state</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
//1.创建组件
class Weather extends React.Component{
//构造器调用几次? ———— 1次
constructor(props){// 借助构造器初始化state
console.log('constructor');
super(props)// 只要你写了构造器 这个一定要写
// react官方要求在下面两种情况下得使用构造器:
// 1. 通过给this.state赋值对象来初始化内部state
// 2. 为事件处理函数绑定实例 这两种情况 分别对应下面两行代码
// 所以在后面state简写方式中 通过别的方式实现了下面两行代码 就可以不用构造器 一般都不写
//初始化状态 state必须是个对象
this.state = {isHot:false,wind:'微风'}
//解决changeWeather中this指向问题
this.changeWeather = this.changeWeather.bind(this)
//构造函数里的this都指向实例对象
// this.changeWeather.bind(this)拿到了实例对象上的changeWeather方法 然后通过bind函数将该方法的this改为了实例对象
// bind做了两件事:1. 给你生成一个新的函数 2. 改变函数里的this指向
// this.changeWeather.bind(this)执行完了你得到了一个新的changeWeather函数 这个函数的this指向实例对象
// 然后你把你得到的这个函数赋值给了实例对象的changeWeather属性
// this.changeWeather是给实例对象赋值了一个changeWeather属性
// 以后 你调用changeWeather就是调用changeWeather函数
}
//render调用几次? ———— 1+n次 1是初始化的那次 n是状态更新的次数
render(){
console.log('render');
//读取状态
const {isHot,wind} = this.state
// 注意这里是this.changeWeather 而不是this.changeWeather()
// 后者是调用changeWeather()函数 将其返回值赋值给onClick
// 前者是将changeWeather()函数赋值给onClick 当你点击的时候 会执行onClick 就会执行changeWeather()函数
// 由于类给类中所有自定义方法都自动开启了严格模式(局部) 这里的this是undefined
// 所以如果你不写this.changeWeather = this.changeWeather.bind(this) 会报错
// 写了之后 this.changeWeather指的就是实例对象的属性 这个属性中保存了一个方法
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
//changeWeather调用几次? ———— 点几次调几次
changeWeather(){
//changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
//由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
//类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
console.log('changeWeather');
//获取原来的isHot值
const isHot = this.state.isHot
//严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换。
this.setState({isHot:!isHot})
console.log(this);
//严重注意:状态(state)不可直接更改,下面这行就是直接更改!!!
//this.state.isHot = !isHot //这是错误的写法
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
</body>
</html>
- 简写:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>state简写方式</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
//1.创建组件
class Weather extends React.Component{
//类中可以直接写赋值语句,如下代码的含义是:给实例对象添加一个属性,名为state
//初始化状态
state = {isHot:false,wind:'微风'}
render(){
const {isHot,wind} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
//自定义方法————要用赋值语句的形式+箭头函数
/*
changeWeather = function() {
// 这也相当于 给实例对象赋值了一个属性changeWeather 该属性指向一个函数
// 但是这样写的话 你还是要在构造函数中修改this的指向
// 但是如果你把changeWeather写成箭头函数 就可以不修改this指向了
// 因为构造函数的this在这里就指向实例对象
const isHot = this.state.isHot
this.setState({isHot:!isHot})
}
*/
changeWeather = ()=>{
// 这里的this指向实例对象
const isHot = this.state.isHot
this.setState({isHot:!isHot})
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
</body>
</html>
props
- 通过标签属性从组件外向组件内传递变化的数据,这些数据保存在props中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>props基本使用</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<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+1}</li>
</ul>
)
}
}
//渲染组件到页面
/*
ReactDOM.render(<Person name="jerry" age="19" sex="男"/>,document.getElementById('test1'))
上面那种写法传入的19是个字符串 你如果写age=19会报错 数据类型是JS才有的 JSX没有
所以你可以写age={19}来告诉它是个Number类型
*/
ReactDOM.render(<Person name="jerry" age={19} sex="男"/>,document.getElementById('test1'))
ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))
const p = {name:'老刘',age:18,sex:'女'}
// console.log('@',...p);// @,
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
// Babel+react确实可以让...展开一个对象 但是只能用于标签属性的传递
// 传递props和传递标签属性一个意思
</script>
</body>
</html>
展开运算符:
let arr1 = [1,3,5,7,9]
let arr2 = [2,4,6,8,10]
console.log(...arr1); //展开一个数组
let arr3 = [...arr1,...arr2]//连接数组
function sum(...numbers){//在函数中使用
return numbers.reduce((preValue,currentValue)=>{
return preValue + currentValue
})
}
console.log(sum(1,2,3,4));
//构造字面量对象时使用展开语法
let person = {name:'tom',age:18}
let person2 = {...person}// 在外面包一个{}就能展开对象了 其实是深拷贝了对象赋值给person2
//console.log(...person); //报错,展开运算符不能展开对象
person.name = 'jerry'
console.log(person2);
console.log(person);
//合并
let person3 = {...person,name:'jack',address:"地球"}
console.log(person3);
- props是只读的 你如果想改props中的值 在组件渲染时进行传值时就应该改 其他时候改不了
- 引入prop-types包 为Person类添加propTypes属性 在该属性中对props中的数据进行限制 通过键值对的形式进行限制 键是props中的数据 值是你的限制 通过
PropTypes.限制.限制
进行限制 - 为Person类添加defaultProps属性 为该属性中props中的数据设置默认值 通过键值对形式进行设置
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>对props进行限制</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
//创建组件
class Person extends React.Component{
render(){
// console.log(this);
const {name,age,sex} = this.props
//props是只读的 你如果想改props中的值 在下面渲染时进行传值时就应该改
// 在这里面props是不能更改的 他是只读的
//this.props.name = 'jack' //此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
//对标签属性进行类型、必要性的限制 你只要给类添加propTypes属性我就认为你在加规则
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串 通过在后面.来加限制
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
speak:PropTypes.func,//限制speak为函数
}
//指定默认标签属性值 通过defaultProps指定默认值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
//渲染组件到页面
ReactDOM.render(<Person name={100} speak={speak}/>,document.getElementById('test1'))
ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))
const p = {name:'老刘',age:18,sex:'女'}
// console.log('@',...p);
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
function speak(){
console.log('我说话了');
}
</script>
</body>
</html>
- 对标签属性进行类型、必要性的限制以及设置默认值 只要给组件本身添加了propTypes、defaultProps属性即可 所以可以使用static关键字为类添加这两个属性 在类中使用static关键字添加的属性在类自身身上
简写:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>对props进行限制</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
//创建组件
class Person extends React.Component{
constructor(props){
// 构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
// 官网:在react组件挂载之前 会调用他的构造函数 在为React.Component子类实现构造函数时
// 应在其他语句之前调用super(props) 否则this.props在构造函数中可能会出现未定义的bug
// 你在构造函数中使用this.props时 一定要给构造函数传props 并且第一句代码就是super(props)
// 要把props传给super 但一般在构造函数中都不用this.props
// console.log(props);
super(props)
// this.props拿的并不是传给constructor的参数 而是实例对象上的props
console.log('constructor',this.props);
}
// 对标签属性进行类型、必要性的限制 只要给类本身添加了propTypes属性就可以做限制
// 不管你是在类外面用.添加的 还是在类里面用static添加的 下同
static propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
//指定默认标签属性值
static defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
render(){
// console.log(this);
const {name,age,sex} = this.props
//props是只读的
//this.props.name = 'jack' //此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1'))
</script>
</body>
</html>
- 对标签属性进行类型、必要性的限制以及设置默认值 只要给组件本身添加了propTypes、defaultProps属性即可 所以针对函数式组件 我们也可以对他的props做限制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>对props进行限制</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
//创建组件
function Person (props){
const {name,age,sex} = props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
// 只要给组件添加了propTypes属性 就可以限制 下同
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
//指定默认标签属性值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1'))
</script>
</body>
</html>
ref
- 组件可以定义ref属性来标识自己
- ref:通过键值对的方式存储 键是ref的值 值是ref当前所处的节点 是虚拟DOM转成真实DOM后的节点
- 通过
this.refs.ref名字
可以获取ref名字
所在组件 - 字符串形式的ref
class Demo extends React.Component{//创建组件
showData = ()=>{//展示左侧输入框的数据
// const input1 = this.refs.input1;
const {input1} = this.refs// 等价于上面那行代码 只不过这里用解构赋值
alert(input1.value)
}
showData2 = ()=>{//展示右侧输入框的数据
const {input2} = this.refs
alert(input2.value)
}
//下面使用的是字符串形式的ref 效率不高 不推荐使用
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'))
- 回调函数形式的ref 框架同上
class Demo extends React.Component{//创建组件
showData = ()=>{//展示左侧输入框的数据
// 你下面this.input1 = c 将节点赋值给了组件实例对象的input1属性 所以这里通过实例获得节点
const {input1} = this
alert(input1.value)
}
showData2 = ()=>{//展示右侧输入框的数据
const {input2} = this
alert(input2.value)
}
render(){
return(
<div>
/*
ref中的函数是个回调函数 回调函数:你定义的 你没调用 它执行了
react会new一个Demo实例 然后调用实例的render方法 执行render会返回jsx 执行jsx时发现ref是个回调函数
就会帮你调用这个回调函数 且在调用时会把当前你处于的节点作为实参传进去
this.input1 = c就是把你处在的节点赋值给实例对象的属性 c就是你当前处于的节点input
*/
<input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={c => this.input2 = c} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
- ref回调函数执行次数问题:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3_回调ref中回调执行次数的问题</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
class Demo extends React.Component{//创建组件
state = {isHot:false}
showInfo = ()=>{
const {input1} = this
alert(input1.value)
}
changeWeather = ()=>{
const {isHot} = this.state//获取原来的状态
this.setState({isHot:!isHot})//更新状态
}
saveInput = (c)=>{
this.input1 = c;
console.log('@',c);
}
/*
下面注释了的那行代码是以内联函数的方式定义的 在组件更新过程中他会被执行两次 第一次传入参数null 第二次传入参数的DOM元素
这是因为在每次渲染时会创建一个新的函数实例 所以react清空旧的ref并设置新的
通过将ref的回调函数定义成class的绑定函数(被注释的代码下一行的写法) 可以避免以上问题 但大多数时候 他是无关紧要的
*/
render(){
const {isHot} = this.state
return(
<div>
<h2>今天天气很{isHot ? '炎热':'凉爽'}</h2>
{/*<input ref={(c)=>{this.input1 = c;console.log('@',c);}} type="text"/><br/><br/>*/}
{/* 把函数放在了实例自身 就算更新组件时重新调用render函数 也不会重新执行ref里的代码 */}
<input ref={this.saveInput} type="text"/><br/><br/>
<button onClick={this.showInfo}>点我提示输入的数据</button>
<button onClick={this.changeWeather}>点我切换天气</button>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>
</body>
</html>
- createRef创建ref容器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>4_createRef</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
class Demo extends React.Component{//创建组件
/*
React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的
下面用了多少个ref 这里就要用React.createRef()创建几个ref
*/
myRef = React.createRef()// 创建一个容器 这个容器挂载到了实例自身的myRef属性上
myRef2 = React.createRef()
showData = ()=>{//展示左侧输入框的数据
// alert(this.myRef);// 输出一个对象current: 节点类型/标签名
// this.myRef.current就可以拿到ref={this.myRef}所在的节点
alert(this.myRef.current.value);// 取节点的值
}
showData2 = ()=>{//展示右侧输入框的数据
alert(this.myRef2.current.value);
}
// 执行到ref={this.myRef}时 react发现你是用createRef()创建的ref 会将ref所在节点存储在容器里
render(){
return(
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>
</body>
</html>
事件处理
- 通过onXxx属性指定事件处理函数(注意大小写)
(1)为了更好的兼容性,React使用的是自定义(合成)事件,而不是使用的原生DOM事件
(2)为了的高效,React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) - 通过
event.target
得到发生事件的DOM元素对象(注意:不要过度使用ref)
class Demo extends React.Component{//创建组件
//创建ref容器
myRef = React.createRef()
myRef2 = React.createRef()
showData = (event)=>{//展示左侧输入框的数据
console.log(event.target);
alert(this.myRef.current.value);
}
// 失去焦点时react会帮你调用showData2 并把发生事件的对象作为实参传递给了showData2
// event.target就可以获得发生事件的事件源
showData2 = (event)=>{//展示右侧输入框的数据
alert(event.target.value);
}
render(){
return(
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
{/* 下面这个输入框失去焦点时会alert alert中提示的是下面这个输入框中的数据
发生事件的DOM元素和你要操作的DOM元素是一个 这种情况就可以省略ref */}
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
收集表单数据
- 非受控组件:页面上所有输入类DOM(input checkbox radio等)的值都是现用现取。在下面的例子中,你点击了登录按钮,然后走了handleSubmit这个回调,在回调中取了节点,然后取了节点的value值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>1_非受控组件</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
class Login extends React.Component{//创建组件
handleSubmit = (event)=>{
/*
表单提交是一个默认的动作 就算你没有写action他也会提交 他的提交会提交数据 刷新页面
但我们希望我们能获得表单的数据 然后用Ajax来发送请求 因为Ajax不会刷新页面 用户体验感更好
也就是说 我现在只想获得表单中的数据 不提交表单
*/
event.preventDefault() //阻止表单提交
const {username,password} = this
alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)
}
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username = c} type="text" name="username"/>
密码:<input ref={c => this.password = c} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
//渲染组件
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
</body>
</html>
- 受控组件:页面上所有输入类DOM(input checkbox radio等)的值,随着你的输入,都会被维护到状态state里,等需要用时,直接从状态中取出来,类似于vue的双向数据绑定,但react没有实现双向数据绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>2_受控组件</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
class Login extends React.Component{//创建组件
state = {//初始化状态
username:'', //用户名
password:'' //密码
}
saveUsername = (event)=>{//保存用户名到状态中
this.setState({username:event.target.value})
}
savePassword = (event)=>{//保存密码到状态中
this.setState({password:event.target.value})
}
handleSubmit = (event)=>{//表单提交的回调
event.preventDefault() //阻止表单提交
const {username,password} = this.state
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
// 所有输入类DOM都有一个onchange回调函数 输入类DOM发生改变时会调用这个回调
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text" name="username"/>
密码:<input onChange={this.savePassword} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
//渲染组件
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
</body>
</html>
函数柯里化
- 高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
(1)若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
(2)若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。 - 常见的高阶函数有:Promise、setTimeout、arr.map()等等
- 函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
function sum(a){
return(b)=>{
return (c)=>{
return a+b+c
}
}
}
上一节的代码使用函数柯里化:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>高阶函数_函数柯里化</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
class Login extends React.Component{//创建组件
state = {//初始化状态
username:'', //用户名
password:'' //密码
}
/*
我们在受控组件里写的代码如下:
saveUsername = (event)=>{//保存用户名到状态中
this.setState({username:event.target.value})
}
savePassword = (event)=>{//保存密码到状态中
this.setState({password:event.target.value})
}
我们现在想统一收集数据到状态中 可以通过传参的方式区分数据 但是在render函数中
react会自己帮你执行方法saveFormData('username')并将username传进去
但我们希望输入框中的内容改变时 onChange被触发时 再收集数据 所以在saveFormData方法中 我们可以返回一个函数
这个函数被赋给了onChange 当输入框中的内容改变时 react会执行onChange方法 将event作为参数传进去
*/
saveFormData = (dataType)=>{//保存表单数据到状态中
return (event)=>{
// [dataType]获得对象的属性
this.setState({[dataType]:event.target.value})
}
}
handleSubmit = (event)=>{//表单提交的回调
event.preventDefault() //阻止表单提交
const {username,password} = this.state
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>
密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
//渲染组件
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
</body>
</html>
- 不使用柯里化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>2_不用函数柯里化的实现</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
//创建组件
class Login extends React.Component{
state = {//初始化状态
username:'', //用户名
password:'' //密码
}
saveFormData = (dataType,event)=>{//保存表单数据到状态中
this.setState({[dataType]:event.target.value})
}
handleSubmit = (event)=>{//表单提交的回调
event.preventDefault() //阻止表单提交
const {username,password} = this.state
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
/*
onChange是个回调函数 保存在这里面的应该是个函数 当输入框的内容改变了 会执行onChange函数
那么我们给他个函数呗 当输入框的内容改变了 会执行onChange指向的箭头函数 并把event作为实参传进去
这个箭头函数调用了收集数据的方法
*/
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={event => this.saveFormData('username',event) } type="text" name="username"/>
密码:<input onChange={event => this.saveFormData('password',event) } type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
//渲染组件
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
</body>
</html>
生命周期函数
- 组件从创建到死亡它会经历一些特定的阶段。
- React组件中包含一系列勾子函数(生命周期回调函数),会在特定的时刻调用。
- 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。
- 生命周期流程图(旧)
生命周期的三个阶段:
(1)初始化阶段: 由ReactDOM.render()
触发初次渲染
constructor()
componentWillMount()
render()
componentDidMount():常用 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
(2)更新阶段: 由组件内部this.setSate()
或父组件render
触发
shouldComponentUpdate()
componentWillUpdate()
render():必须使用的一个
componentDidUpdate()
(3)卸载组件: 由ReactDOM.unmountComponentAtNode()
触发
componentWillUnmount():常用 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>2_react生命周期(旧)</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel">
class Count extends React.Component{//创建组件
constructor(props){//构造器
console.log('Count---constructor');
super(props)
this.state = {count:0}//初始化状态
}
add = ()=>{//加1按钮的回调
const {count} = this.state//获取原状态
this.setState({count:count+1})//更新状态
}
death = ()=>{//卸载组件按钮的回调
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
force = ()=>{//强制更新按钮的回调
//forceUpdate不会调用shouldComponentUpdate询问是否能更新页面 而是直接更新页面
this.forceUpdate()
}
componentWillMount(){//组件将要挂载的钩子
console.log('Count---componentWillMount');
}
componentDidMount(){//组件挂载完毕的钩子
console.log('Count---componentDidMount');
}
componentWillUnmount(){//组件将要卸载的钩子
console.log('Count---componentWillUnmount');
}
// 控制组件更新的“阀门”
// 你可以不写这个钩子函数 不写react会自动帮你创建一个 且返回值为true
// 但是如果你自己写了这个钩子函数 必须返回一个布尔值 返回true表示可以更新组件 就会执行后面的钩子函数
// 返回false表示不准更新组件 后面的钩子函数也就不会执行
shouldComponentUpdate(){
console.log('Count---shouldComponentUpdate');
return true
}
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>
)
}
}
class A extends React.Component{//父组件A
state = {carName:'奔驰'}//初始化状态
changeCar = ()=>{
this.setState({carName:'奥拓'})
}
render(){
return(
<div>
<div>我是A组件</div>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName}/>
</div>
)
}
}
class B extends React.Component{//子组件B
//组件将要接收新的props的钩子
// 第一次不算 也就是说初始不算 更新才算 可以接受props作为参数 props中放的就是传过来的参数
componentWillReceiveProps(props){
console.log('B---componentWillReceiveProps',props);
}
shouldComponentUpdate(){//控制组件更新的“阀门”
console.log('B---shouldComponentUpdate');
return true
}
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/>,document.getElementById('test'))
</script>
</body>
</html>
- 生命周期流程图(新)
生命周期的三个阶段:
(1)初始化阶段: 由ReactDOM.render()
触发初次渲染
constructor()
getDerivedStateFromProps
render()
componentDidMount():常用 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
(2)更新阶段: 由组件内部this.setSate()
或父组件重新render
触发
getDerivedStateFromProps
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate
componentDidUpdate()
(3)卸载组件: 由ReactDOM.unmountComponentAtNode()
触发
componentWillUnmount():常用 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3_react生命周期(新)</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/17.0.1/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/17.0.1/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/17.0.1/babel.min.js"></script>
<script type="text/babel">
class Count extends React.Component{//创建组件
constructor(props){//构造器
console.log('Count---constructor');
super(props)
//若state的值在任何时候都取决于props 你也可以在构造函数中 将取得的props用来初始化state
this.state = {count:0}//初始化状态
}
add = ()=>{//加1按钮的回调
const {count} = this.state//获取原状态
this.setState({count:count+1})//更新状态
}
death = ()=>{//卸载组件按钮的回调
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
force = ()=>{//强制更新按钮的回调
this.forceUpdate()
}
//若state的值在任何时候都取决于props,你的修改 你的初始化都没有用 完全依赖props 那么可以使用getDerivedStateFromProps
//也可以在这里做一些判断:你传过来的props的某个属性的值是多少 初始化中某个属性的值是多少
// 然后进行对比 奇数以谁为主 偶数以谁为主
//只要有getDerivedStateFromProps 你所有的状态都以他的返回值为主 getDerivedStateFromProps在挂载和更新阶段都是必经之路
static getDerivedStateFromProps(props,state){// 他是类自身的方法 state是初始化状态的值
console.log('getDerivedStateFromProps',props,state);
return null// 必须返回一个状态对象或null 状态对象就是state中某个键的值 如return {count: 180}
// 只要返回一个对象 如果对象里包含的键在初始state中也有 该键的值就以我们这里返回的为主 不以初始化为主
// 而且以后这个键的值都改不了了
}
getSnapshotBeforeUpdate(){//在更新之前获取快照
console.log('getSnapshotBeforeUpdate');
return 'atguigu'// 必须有返回值 可以返回null
}
componentDidMount(){//组件挂载完毕的钩子
console.log('Count---componentDidMount');
}
componentWillUnmount(){//组件将要卸载的钩子
console.log('Count---componentWillUnmount');
}
shouldComponentUpdate(){//控制组件更新的“阀门”
console.log('Count---shouldComponentUpdate');
return true
}
//组件更新完毕的钩子 preProps是组件更新前的props preState是组件更新前的state snapshotValue是getSnapshotBeforeUpdate的返回值
//getSnapshotBeforeUpdate会将返回值传给componentDidUpdate的snapshotValue
componentDidUpdate(preProps,preState,snapshotValue){
console.log('Count---componentDidUpdate',preProps,preState,snapshotValue);
}
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>
)
}
}
//渲染组件
ReactDOM.render(<Count count={199}/>,document.getElementById('test'))
</script>
</body>
</html>
- 新旧生命周期函数的区别:新的废弃了3个:
componentWillMount() componentWillUpdate() componentWillReceiveProps
新增了2个钩子函数:getDerivedStateFromProps getSnapshotBeforeUpdate
- 重要的勾子:
(1)render:初始化渲染或更新渲染调用
(2)componentDidMount:开启监听, 发送ajax请求
(3)componentWillUnmount:做一些收尾工作, 如: 清理定时器 - 即将废弃的勾子:
(1)componentWillMount
(2)componentWillReceiveProps
(3)componentWillUpdate
现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。
getSnapShotBeforeUpdate的使用场景
- 我们现在想实现一个新闻展示区 新闻一条条的返回 将新的新闻展示在顶部 新闻越来越多 但是展示区高度有限 所以会产生滚动条 因为每隔1s就会返回一条数据 假如现在数据展示到200条了 但是我想看第4条数据 我滑到第4条 但是因为每1s会返回条新数据展示在顶部 所以我的第4条数据会慢慢滚下去 但是我希望它不动
- 实现思路:在组件更新前 也就是
getSnapshotBeforeUpdate
中获取当前列表的高度并传给componentDidUpdate
,组件更新后 新的数据添加到了展示区顶部 我们再去获取当前列表的高度 两个高度相减就得到多出来的那部分高度 此时组件的位置就应该是原来的位置加上多出来的高度 设置给组件即可
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>4_getSnapShotBeforeUpdate的使用场景</title>
<style>
.list{
width: 200px;
height: 150px;
background-color: skyblue;
overflow: auto;
}
.news{
height: 30px;
}
</style>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/17.0.1/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/17.0.1/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/17.0.1/babel.min.js"></script>
<script type="text/babel">
class NewsList extends React.Component{
state = {newsArr:[]}// 状态中的数据驱动页面的显示
componentDidMount(){
setInterval(() => {
const {newsArr} = this.state//获取原状态
const news = '新闻'+ (newsArr.length+1)//模拟一条新闻
this.setState({newsArr:[news,...newsArr]})//更新状态
}, 1000);
}
getSnapshotBeforeUpdate(){
return this.refs.list.scrollHeight
}
componentDidUpdate(preProps,preState,height){
this.refs.list.scrollTop += this.refs.list.scrollHeight - height
}
render(){
return(
<div className="list" ref="list">
{
this.state.newsArr.map((n,index)=>{
return <div key={index} className="news">{n}</div>
})
}
</div>
)
}
}
ReactDOM.render(<NewsList/>,document.getElementById('test'))
</script>
</body>
</html>
虚拟DOM与DIFF算法
- 两个虚拟DOM进行比较 比较的是标签
- 下面代码中 每秒中会获取当前时间 时间保存在state中 state中的内容每秒都变 变了就会来调render函数 会获得一个新的虚拟DOM 新旧虚拟DOM进行比较 他们的
<h1>hello</h1><input type="text"/>
没变 所以真实DOM也不会改变 但是span标签中的{this.state.date.toTimeString()}
每次都在改变 所以他会更新span标签的内容现在是:{this.state.date.toTimeString()}
不能说“现在是”这三个字没变就不更新了哦 DIFF算法比较的是标签 其次 他不会更新<input type="text"/>
因为两个虚拟DOM进行比较 比较的标签,这也是个标签 还是会进行新旧虚拟DOM比较 发现这个标签没变 就不会改变真实虚拟DOM
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>验证diff算法</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/17.0.1/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/17.0.1/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/17.0.1/babel.min.js"></script>
<script type="text/babel">
class Time extends React.Component {
state = {date: new Date()}
componentDidMount () {
setInterval(() => {
this.setState({
date: new Date()
})
}, 1000)
}
render () {
return (
<div>
<h1>hello</h1>
<input type="text"/>
<span>
现在是:{this.state.date.toTimeString()}
<input type="text"/>
</span>
</div>
)
}
}
ReactDOM.render(<Time/>,document.getElementById('test'))
</script>
</body>
</html>
key的作用
react/vue中的key有什么作用?(key的内部原理是什么?)为什么遍历列表时,key最好不要用index?
- 虚拟DOM中key的作用:
(1)简单的说:key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
(2)详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:若虚拟DOM中内容没变, 直接使用之前的真实DOM 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key 根据数据创建新的真实DOM,随后渲染到到页面 - 用index作为key可能会引发的问题:
(1)若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 界面效果没问题, 但效率低。
(2)如果结构中还包含输入类的DOM:会产生错误DOM更新 界面有问题。
(3)注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。 - 开发中如何选择key?
(1)最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
(2)如果确定只是简单的展示数据,用index也是可以的。
class Person extends React.Component{
state = {
persons:[
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
]
}
add = ()=>{
const {persons} = this.state
const p = {id:persons.length+1,name:'小王',age:20}
this.setState({persons:[p,...persons]})
}
render(){
return (
<div>
<h2>展示人员信息</h2>
<button onClick={this.add}>添加一个小王</button>
<h3>使用index(索引值)作为key</h3>
<ul>
{
this.state.persons.map((personObj,index)=>{
return <li key={index}>{personObj.name}---{personObj.age}<input type="text"/></li>
})
}
</ul>
<hr/>
<hr/>
<h3>使用id(数据的唯一标识)作为key</h3>
<ul>
{
this.state.persons.map((personObj)=>{
return <li key={personObj.id}>{personObj.name}---{personObj.age}<input type="text"/></li>
})
}
</ul>
</div>
)
}
}
ReactDOM.render(<Person/>,document.getElementById('test'))
慢动作回放----使用index索引值作为key
初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM:
<li key=0>小张---18<input type="text"/></li>
<li key=1>小李---19<input type="text"/></li>
更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的虚拟DOM:
<li key=0>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
比较初始的虚拟DOM和更新数据后的的虚拟DOM 发现key=0的两个标签内容不一样
所以将更新数据后的虚拟DOM:<li key=0>小王---20<input type="text"/></li>
转成真实DOM挂载到页面 同理 key=1 key=2的两个标签都会转换成真实DOM挂载到页面
可是key=1 key=2这两个标签明明就可以复用 因为你设置的不正确的key导致没有必要的真实DOM更新 性能变差
-----------------------------------------------------------------
慢动作回放----使用id唯一标识作为key
初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM:
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的的虚拟DOM:
<li key=3>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
比较初始的虚拟DOM和更新数据后的的虚拟DOM 将key=3的标签转换成真实DOM挂载到页面 其余标签不动 复用之前的真实DOM
React应用(基于脚手架)
- React的脚手架是用webpack搭建的
- 创建项目:
(1)全局安装:cmd输入:npm i -g create-react-app
(2)在cmd中进入想创建项目的目录 然后在该目录下输入:create-react-app 项目名
yarn start:开启开发者的服务器 每次写完代码想看效果都要执行该命令
yarn build:把你写完的项目最终进行一次打包
- package.json的script中可以查看短命令
- react是单页面应用 整个页面只有一个HTML文件
- 项目启动后 首先来到了src下的index.js(入口文件) 引入了一堆库 然后触发渲染App 然后他会去public中找index.html 在index.html中找相应id的容器(本文是root) 然后App组件就出现在了页面 并设置了样式 其中index.js index.html不能改名
- 在react中 引入js文件和jsx文件都可以省略后缀
- 项目中文件名.jsx只是用来区分这个文件是个组件 其实你写文件名.js也行
脚手架配置代理服务器
在终端输入:npm i axios
配置单个代理服务器
客户端:http://localhost:3000
服务器:http://localhost:5000
代理服务器:http://localhost:3000
两台服务器传数据不用ajax,用的http请求,没有同源限制
- 浏览器向服务器发送请求 服务器收到请求 然后返回数据 但是由于浏览器同源协议的限制 返回的数据被浏览器的Ajax引擎拦截了
- 使用代理服务器解决跨域:客户端给代理服务器发请求 代理服务器将浏览器的请求转发给服务器 服务器给代理服务器返回数据 代理服务器将服务器返回的数据返回给浏览器 代理服务器可以收到服务器发回来的数据 因为代理服务器没有Ajax引擎
- 懂了上面的逻辑 那么浏览器就应该给代理服务器发送请求:
axios.get('http://localhost:3000/students')……
然后在package.json中追加如下配置:"proxy":"http://localhost:5000"
这是开启代理服务器"proxy":"服务器地址"
- 以后 浏览器请求的资源 若3000(代理服务器)没有 则把请求转发给5000(服务器)
否则3000(代理服务器)会直接返回自己这里的资源给浏览器 而不转发给5000(服务器)
配置多个代理服务器
- 创建代理配置文件 在src下创建配置文件:
src/setupProxy.js
在setupProxy.js中使用conmonJS写代码 react脚手架会找到这个文件 把该文件加到webpack的配置中 webpack中用的都是node中的语法 写的都是conmonJS。setupProxy.js代码如下:
const proxy = require('http-proxy-middleware')
module.exports = function(app){
app.use(
proxy('/api1',{
//遇见/api1前缀的请求,就会触发该代理配置 只要你的请求中有api1 就会转发该请求
target:'http://localhost:5000', //请求转发给谁
changeOrigin:true,//控制服务器收到的请求头中Host的值
// 若不写上面这行 则默认取值为false 服务器会知道这个请求是从哪里过来的(从浏览器3000端口过来的 不是代理服务器)
// 若取值为true 服务器会错误的认为这个请求是从我自己同源的来的 相当于欺骗服务器
pathRewrite:{'^/api1':''} //重写请求路径(必须) 去除/api1
// 浏览器想请求服务器(http://localhost:5000)的/students这个资源
// 你正常发请求 你的URL(http://localhost:5000/students)中不应该出现api1的 但是你配置了代理服务器
// 如果URL中没有api1 代理服务器不会给你转发
// 但是你加了api1 路径又不对了 所以我们在这里把/api1去除掉
}),
proxy('/api2',{
target:'http://localhost:5001',
changeOrigin:true,
pathRewrite:{'^/api2':''}
}),
)
}
- 然后浏览器发送的请求记得加上/api1:
axios.get('http://localhost:3000/api1/students')
/api1必须写在端口号后面 - 以后 浏览器请求的资源 若3000(代理服务器)没有 则把请求转发给5000(服务器)
否则3000(代理服务器)会直接返回自己这里的资源给浏览器 而不转发给5000(服务器)(?)
消息订阅与发布
- 在终端下载:
npm install pubsub-js --save
- 使用:
(1)引入:import PubSub from 'pubsub-js'
(2)订阅消息:在componentDidMount
中订阅消息 即组件一挂载就订阅消息:let token = PubSub.subscribe('delete', function(msg, data){ });
delete是消息名 你要订阅的消息 如果有人发布了delete这个消息 你就会收到通知 收到之后会调用回调函数 回调函数中的msg就是消息名 也就是delete data是别人想交给你的数据 你每次订阅消息都会有一个订阅的token 等以后你不想订阅这个消息了(如组件被卸载了) 可以通过token取消订阅:PubSub.unsubscribe(token);
(3)发布消息:PubSub.publish('delete', data)
delete是消息名 第二个参数data是携带的数据
(4)取消订阅:在componentWillUnmount
中取消订阅 即订阅消息的组件将被卸载了 取消订阅消息:PubSub.unsubscribe(token);
- 任意组件进行通信都可以使用消息订阅与发布
fetch发送请求
- fetch:原生函数,不再使用XmlHttpRequest对象提交ajax请求