【React自学笔记01】来喽~react新手村打卡
一、React简介
1.1 什么是React
用于动态构建用户界面的 JavaScript 库(只关注于视图)
1.2 为什么学React
原生JavaScript的缺点:
-
原生JavaScript操作dom繁琐,效率低(DOM-API操作UI)
-
使用JavaScript每次操作dom都要进行浏览器重绘、重排
-
JavaScript没有组件化编码方案,模块化仅仅是对js进行了拆分,复用率低
React的特点:
-
组件化模式,声明式编码
- 相比于之前的命令式编码,提高了开发效率和组件复用率
-
在react native 中使用React进行移动端开发
-
使用虚拟DOM+Diffing算法
-
原生js操作真实dom,每次数据发生变化时,都会重新渲染数据
-
操作虚拟dom存放内存之后再映射成页面上的真实dom。当数据发生改变时,新生成的虚拟dom会和之前的虚拟dom比较,如果一样则不再生成新的真实dom,直接用旧的真实dom。
-
DOM Diffing算法, 最小化页面重绘
-
1.3 React基本使用
1.3.1 相关js库
-
react.js:React核心库。
-
react-dom.js:提供操作DOM的react扩展库,支持react操作dom。
-
babel.min.js:解析JSX语法代码转为JS代码的库。
1.3.2 虚拟dom的两种创建方式
- 使用jsx创建虚拟dom
<body>
<!--定义了一个容器-->
<div id="test"></div>
<!--tips1:核心库和扩展库顺序不能颠倒-->
<script type="text/javascript" src="../js/react.development.js"></script>
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<script type="text/javascript" src="../js/babel.min.js"></script>
<!--tips2:type类型必须为babel,用于将jsx编译为js-->
<script type="text/babel">
//step1:创建虚拟dom 注意jsx中变量可以是标签
const VDom = <h1>hello,react</h1>
//step2:将虚拟dom渲染到页面 ReactDOM是关键字
// param1:渲染哪个虚拟dom,param2:渲染到哪个容器中
ReactDOM.render(VDom, document.getElementById('test'))
</script>
</body>
- 使用js创建虚拟dom(不推荐)
const VDom = React.createElement('h1', { id: 'title' }, 'hello react~')
ReactDOM.render(VDom, document.getElementById('test'))
总结:
-
jsx的存在简化了虚拟dom的写法,当需要多层标签时只需要外面包一个括号即可进行标签的缩进
-
jsx的写法本质就是js写法的语法糖,更加便捷
-
关于虚拟dom:
-
本质是一个Object对象,属性少
-
虚拟dom最终会被React转换为真实dom
-
1.4 jsx语法规则
-
定义虚拟dom时不要写引号
-
标签中混入js表达式时要用{}
-
样式类名指令依然写在标签中,但是不要写class,写className
-
内联样式要用style={{key:value}}形式写
(1)外层{}代表js表达式
(2)内层{}代表对象
-
根标签只能有一个
-
标签必须闭合
-
标签首字母
(1)若是小写字母,必须是html中存在的标签,若html中无该标签对应的同名元素,报错;
(2)若是大写字母,react就去渲染对应的组件,若组件未定义,报错
-
jsx中注释的写法
<script type="text/babel">
const mId = 'aTguiGU'
const mData = 'hello react'
const VDOM = (
<div>
<div id={mId} className="title">
<span style={{ color: 'white' }}>{mData}</span>
</div>
<input type="text"></input>
</div>
)
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
注意:
- 区分js表达式和js语句(代码)
-
表达式:一个表达式产生一个值,可以放在任何一个需要的地方
- 判断技巧在表达式左侧加const variable=是否成立
- a
- a+b
- demo(1)函数调用表达式
- arr.map()
- function test(){}
-
语句(代码):
- if(){}
- for(){}
- switch()
- 循环渲染的方法[类似于vue中的v-for逻辑]
<script type="text/babel">
const data = ['Angular', 'Vue', 'React']
const VDOM = (
<div>
<h1>前端js框架</h1>
<ul>
{
data.map((item, i) => {
return <li key={i}>{item}</li>
})
}
</ul>
</div>
)
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
1.5 组件与模块
1.5.1 模块及模块化
-
理解模块:把一个庞大的js工程拆成一个js,一个功能就是一个js文件
-
为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂
-
作用:复用js,简化js编写,提高js运行效率
-
模块化:当应用的js都以模块来编写,该应用就是模块化应用
1.5.2 组件及组件化
-
理解组件:所有实现某一效果功能的资源(html、css、image等)、js全部打包为一个组件,就像拼乐高一样
-
为什么用:一个界面的功能更复杂
-
作用:复用编码、简化项目编码、提高运行效率
-
组件化:当应用是以多组件的方式实现,这个应用就是一个组件化应用
——————————————记录于2022.05.11——————————————
二、React面向组件编程
2.1 组件的两种定义方式
2.1.1 函数式组件
📌定义步骤:
-
定义函数式组件:
-
function定义的函数组件名首字母大写MyComponent
-
babel开启严格模式禁止自定义函数中的this指向window,this指向undefined
-
返回值return 要渲染的内容
-
-
渲染组件到页面,引用自定义组件标签
-
ReactDOM.render(<自定义组件名MyComponent/>,渲染到哪个容器中)
-
标签要写成闭合样式
💕注意:执行了第二步之后发生了什么?
-
1)React解析组件标签,找到MyComponent组件
-
2)发现该组件是函数定义的组件,随后调用该函数,将返回的虚拟dom转为真实dom,呈现在页面中。
<script type="text/babel">
function MyComponent() {
return <h1>我是函数式组件</h1>
}
ReactDOM.render(<MyComponent />, document.getElementById('test'))
</script>
2.1.2 类式组件
📌定义步骤:
-
定义类式组件:
-
类名首字母大写MyComponent,继承自React.Component{}
-
重写父类函数render(){},返回值 return 要渲染到页面的内容
🎐render放在MyComponent的原型对象上,提供给实例使用
🎐render中的this是MyComponent(组件)的实例对象
-
babel开启严格模式禁止自定义函数中的this指向window,this指向undefined
-
-
渲染组件到页面,引用自定义组件标签
-
ReactDOM.render(<自定义组件名MyComponent/>,渲染到哪个容器中)
-
标签要写成闭合样式
💕注意:执行第二步之后发生了什么?
-
1)React解析组件标签,找到MyComponent组件
-
2)发现组件是类式组件,随后new出来该类的实例,并通过该实例调用到原型上的render方法
-
3)将render返回的虚拟dom转为真实dom,呈现在页面
<script type="text/babel">
class MyComponent extends React.Component {
render() {
return <h1>我是函数式组件</h1>
}
}
ReactDOM.render(<MyComponent />, document.getElementById('test'))
</script>
————————————记录于2022.05.12————————————
2.2 组件实例的三大核心属性
2.2.1 state属性
1. 案例分析
点击组件实现组件实例属性中状态发生改变
📌 借助构造器初始化state
-
在类中利用构造器接受props参数,并将props传递给父类构造器
-
定义state的状态(初始化状态):要将state定义为一个对象{key:value}的形式
class Weather extends React.Component {
constructor(props) {
super(props)
this.state = { isHot: false }
}
render() {
return <h1>今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1>
}
}
ReactDOM.render(<Weather />, document.getElementById('test'))
📌 react中如何绑定事件:(注意原生js中绑定事件的三种方式)
-
onClick={函数名} *注意{}是指js表达式;函数名后面不能加(),因为加了()代表调用函数,将函数的返回值赋值给onClick,这是不对的 不加()就是代表当函数触发后才调用
-
定义函数 function 函数名(){}
class Weather extends React.Component {
constructor(props) {
super(props)
this.state = { isHot: false }
}
render() {
return <h1 onClick={changeWeather} >今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1>
}
}
ReactDOM.render(<Weather />, document.getElementById('test'))
function changeWeather() {
console.log('click');
//接下来的逻辑是:要拿到Weather的实例对象,来改变该实例的state状态属性
}
📌 如何获取到类的实例对象中的属性
以上代码存在的问题:
-
代码结构混乱,组件内部调用了外部函数,造成了this实例对象的丢失,需要再定义变量用来缓存this
-
要将所有关于组件的行为都统一放到组件中
-
也就是说,要把定义的该函数放到组件的原型对象上,供实例使用
-
通过Weather实例调用该方法时,该方法的this就是组件的实例对象
-
组件内调用该方法时,使用this.方法名调用方法
class Weather extends React.Component { constructor(props) { super(props) this.state = { isHot: false } } render() { return <h1 onClick={this.changeWeather} >今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1> } changeWeather() { this.state.isHot = !this.state.isHot } } ReactDOM.render(<Weather />, document.getElementById('test'))
-
-
基于以上思路,仍存在问题
-
构造器中的this一定是当前类的实例对象
-
render()也是放到类的原型上,供实例使用,this指向实例对象
-
但是自定义方法changeWeather中的this此时发生this丢失,其值等于 undefined
❓原因:
function demo(){ console.log(this); } demo()//window function demo(){ 'use strict' console.log(this) } demo();//undefined
直接调用的方法,this指向window;严格模式下方法中的this是undefined
const p1=new Person('tom',18) p1.speak ()//通过实例调用speak方法 const x=p1.speak x()//直接调用 undefined
x()属于直接调用,类中定义的方法已经在局部自动开启了严格模式,所以this是undefined
所以onClick={this.changeWeather}并没有调用该方法,只是通过类的实例对象沿着原型链找到了这个方法,然后把这个方法交给onClick,将changeWeather作为onClick的回调。当点击事件发生,从堆里直接调用方法,并不是实例对象调用的。
💕 总结:
-
changeWeather放在Weather的原型对象上,供实例使用
-
由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
-
类中的方法默认开启局部严格模式,所以changeWeather中的this是undefined
⭐ 解决:
-
因为构造器中的this一定是当前类的实例对象,
-
又changeWeather放在Weather的原型对象上,供实例使用(顺着原型找到原型对象上的changeWeather)
-
在构造器中this.changeWeather.bind(this),bind()函数生成一个新函数,同时将类中的changeWeather方法的this改为传来的this(即Weather的实例对象),然后将这个函数放到了实例的自身(this),起名叫changeWeather->由此得到实例对象身上的方法this.changeWeather
-
所以onClick={this.changeWeather}调用的这个函数是实例对象自身上的changeWeather,而不是原型上的changeWeather ,(拿着原型上的方法生成一个新的方法挂在自身)
-
注意:.bind方法返回一个新函数
class Weather extends React.Component { constructor(props) { super(props) this.state = { isHot: false } this.changeWeather=this.changeWeather.bind(this) } render() { return <h1 onClick={this.changeWeather} >今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1> } changeWeather() { console.log(this); } } ReactDOM.render(<Weather />, document.getElementById('test'))
-
-
📌 实现组件状态更改
-
注意:状态(state)不可直接更改 this.state.isHot=!this.state.isHot (错误)
-
要借助一个内置API更改,必须通过setState进行更新
-
更新的 数据 会和原来state中的不同名数据合并,同名数据替换
-
调用次数问题:
- 构造器调用一次;
- render函数调用1+n次,1是初始化渲染,n是状态发生更新的次数,每更新一次数据,都要重新渲染一次页面;
- changeWeather点几次调用几次
<script type="text/babel">
class Weather extends React.Component {
constructor(props) {
super(props)
this.state = { isHot: false }
this.newChange = this.changeWeather.bind(this)
}
render() {
return <h1 onClick={this.newChange} >今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1>
}
changeWeather() {
// console.log(this);
const isHot = this.state.isHot
this.setState({ isHot: !isHot })
}
}
ReactDOM.render(<Weather />, document.getElementById('test'))
</script>
2. 代码简写形式
-
类中可以直接写赋值语句,给实例对象添加一个属性
-
箭头函数自身没有this,但是在箭头函数中使用this,找的就是外层函数的this作为箭头函数的this->就是组件的实例对象
-
组件中的自定义方法大部分都是作为事件的回调。将自定义方法写成 赋值语句+箭头函数 的形式
class Weather extends React.Component {
constructor(props) {
super(props)
}
state = { isHot: false }
render() {
return <h1 onClick={this.changeWeather} >今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1>
}
changeWeather = () => {
const isHot = this.state.isHot
this.setState({ isHot: !isHot })
}
}
ReactDOM.render(<Weather />, document.getElementById('test'))
💕 总结state:
-
state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)
-
组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)
——————————————记录于2022.05.13——————————————
2.2.2 props属性
1. 基本使用
案例分析:自定义用来显示一个人员信息的组件
class Person extends React.Component {
render() {
const { name, gender, age } = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{gender}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
ReactDOM.render(<Person name="tom" age="13" gender="男" />, document.getElementById('test'))
💕 总结:
-
通过标签属性从组件外向组件内传递变化的数据
-
标签属性必须是key="value"形式 将Value的值默认为字符串形式
-
如果key={value} value值则为传值本身的数据类型
2. 批量传递props(标签属性)
const p={ name:"tom" ,age:"13", gender:"男"}
ReactDOM.render(<Person {...p}/>, document.getElementById('test'))
💕 总结展开运算符的用法:
-
展开数组(...arr1)
-
连接数组[...arr1,...arr2]
-
函数中使用作为形参参数接收(...numbers)
-
构造字面量对象时复制对象
-
合并
💕 注意:
- babel+react可以展开对象{...object},但仅仅适用于标签属性的传递
3. 对props进行限制
从React16开始,有了新写法:使用prop-types库进限制(需要引入prop-types库)
-
引入prop-types,用于对组件标签属性进行限制
-
对标签属性进行类型、必要性的限制
在组件身上添加限制规则:组件名.propTypes React底层会找到他(小写p)
-
指定默认标签属性值
class Person extends React.Component {
render() {
const { name, gender, age } = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{gender}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
Person.propTypes = {
name: PropTypes.string.isRequired,
gender: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func
}
Person.defaultProps = {
gender: '未知',
age: 18
}
ReactDOM.render(<Person name="jack" age={12} gender="男" />, document.getElementById('test1'))
const p = { name: "tom", age: 13, gender: "男" }
ReactDOM.render(<Person {...p} />, document.getElementById('test2'))
💕 注意:props是只读的
4. props简写形式
-
组件中能够完成所有关于组件的东西:对状态初始化、对标签属性的限制、默认值的指定
-
之前我们说类中可以直接写赋值语句,给实例对象添加一个属性,但是在属性前面加一个static,就是给类本身加一个属性(注意千万不要加到render里)
class Person extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
gender: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func
}
static defaultProps = {
gender: '未知',
age: 18
}
render() {
const { name, gender, age } = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{gender}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
ReactDOM.render(<Person name="jack" age={12} gender="男" />, document.getElementById('test1'))
const p = { name: "tom", age: 13, gender: "男" }
ReactDOM.render(<Person {...p} />, document.getElementById('test2'))
5. props和构造器比较
📌 在React中构造器仅用于以下两种情况:
-
通过给this.state赋值对象来初始化内部state(state属性的基本使用案例中用过)
-
为事件处理函数绑定实例
📌 props在构造器中的存在意义:
-
类中构造器不是必须有的,可以省略
-
但是如果构造器不省略,那么props就必须接收props,且作为参数传入super中
-
构造器中是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props
-
类中构造器能省略就省略
6. 函数式组件使用props
在函数式组件中,不能使用state和ref,但是可以使用props
函数式组件对数据的限定规则只能写到函数外面
function Person(props) {
return (
<ul>
<li>姓名:{props.name}</li>
<li>性别:{props.gender}</li>
<li>年龄:{props.age}</li>
</ul>
)
}
Person.propTypes = {
name: PropTypes.string.isRequired,
gender: PropTypes.string,
age: PropTypes.number
}
Person.defaultProps = {
gender: '未知',
age: 18
}
ReactDOM.render(<Person name="Kitty" age={18} gender="女" />, document.getElementById('test'))
💕 总结:
-
每个组件对象都会有props(properties的简写)属性
-
组件标签的所有属性都保存在props中
-
作用:通过标签属性从组件外向组件内传递变化的数据
-
内部读取某个属性值:render函数中this.props.name
-
扩展属性: 将对象的所有属性通过props传递 <Person {...person}/>
——————————————记录于2022.05.14——————————————
2.2.3 ref属性
1. 字符串形式的ref
-
组件内的标签可以定义ref属性来标识自己,相当于原生js里的id </input ref="input1"/>
-
组件实例中{ref:{input1:input}}
-
拿到真实的节点标识 this.refs.input1
-
相当于双向数据绑定 input1.value 就是input输入框中的值
案例分析:自定义组件,功能说明如下:
-
点击按钮,提示第一个输入框中的值
-
当第2个输入框失去焦点时,提示这个输入框中的值
<script type="text/babel">
class Demo extends React.Component {
showData = () => {
alert(this.refs.input1.value)
}
showData2 = () => {
alert(this.refs.input2.value)
}
render() {
return (
<div>
<input ref="input1" placeholder="点击按钮提示数据"></input>
<button onClick={this.showData}>弹框</button>
<input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test'))
</script>
问题:效率不高,不推荐使用,未来可能移除
2. 回调形式的ref
什么是回调:自定义的、自己未调用、别人调用了
参数是ref当前所处的节点
内联函数的形式:(用的比较多)
<script type="text/babel">
class Demo extends React.Component {
showData = () => {
// console.log(this);
const { input1 } = this;
alert(input1.value)
}
showData2 = () => {
alert(this.input2.value)
}
render() {
return (
<div>
<input ref={currentNode => this.input1 = currentNode} placeholder="点击按钮提示数据"></input>
<button onClick={this.showData}>弹框</button>
<input ref={cNode => this.input2 = cNode} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test'))
</script>
类绑定函数形式:
-
把内联形式中的逻辑部分直接抽离出来,挂载在实例上形成一个函数
-
大多数情况下无关紧要
3. createRef形式
-
React.createRef调用后可以返回 一个容器,该容器可以存储被ref所标识的节点
-
该容器专人专用,后放进去的东西会把之前的内容替换掉
-
当前React最推荐的一种形式
<script type="text/babel">
class Demo extends React.Component {
myRef = React.createRef();
showData = () => {
alert(this.myRef.current.value);
}
render() {
return (
<div>
<input ref={this.myRef} type="text" />
<button onClick={this.showData}>点击按钮显示输入数据</button>
</div>
)
}
}
ReactDOM.render(<Demo />, document.getElementById('test'))
</script>
2.2.4 事件处理
-
通过onXXX属性指定事件处理函数(注意大小写)
-
React使用的是自定义(合成)时间,而不是使用的原生DOM事件——为了更好的兼容性
-
React中的时间是通过时间委托方式处理的——为了高效
-
-
通过event.target得到发生时间的DOM元素对象
-
发生事件的dom元素和要操作的dom元素是同一个,要省略ref,采用event.target
-
不要过度使用ref
showData2=(event)=>{ alert(event.target.value); } <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
-
2.3 收集表单数据
2.3.1 非受控组件
非受控组件:页面中所有输入类dom,现用现取
案例:在表单中收入用户名密码,点击登录提示输入信息
-
表单提交默认引发页面跳转,阻止表单的默认行为
-
使用ref逻辑来进行双向数据绑定
<script type="text/babel">
class Login extends React.Component {
getInfo = (event) => {
event.preventDefault()
const { username, password } = this;
alert(`用户名:${username.value} 密码:${password.value}`)
}
render() {
return (
<form onSubmit={this.getInfo}>
用户名:<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>
2.3.2 受控组件
-
输入类dom都可以绑定onchange事件,只要改变数据就会触发onchange指定的回调
-
受控组件:页面中所有输入类dom,随着输入可以把数据维护到状态中去,需要用的时候直接从状态中取出来。类似于vue中的双向数据绑定
尽量使用受控制组件
<script type="text/babel">
class Login extends React.Component {
state = {
username: '',
password: ''
}
saveUsername = (event) => {
const username = this.username
this.setState({ username: event.target.value })
}
savePassword = (event) => {
const password = this.password
this.setState({ password: event.target.value })
}
getInfo = (e) => {
e.preventDefault()
const { username, password } = this.state
alert(`用户名:${username} 密码:${password}`)
}
render() {
return (
<form onSubmit={this.getInfo}>
用户名:<input onChange={this.saveUsername} type="text" name="username" />
密码:<input type="password" onChange={this.savePassword} name="password" />
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login />, document.getElementById('test'))
</script>
——————————————记录于2022.05.15——————————————
2.3.3 函数柯里化
-
上述代码存在冗余问题,如果要实现注册功能,输入的input有多个(用户名、密码、邮箱、电话等),就需要更多的函数完成向state中存值的逻辑。就出现了将相同的逻辑合并成一个函数的 需求。
-
但是 如果写成onChange={this.saveFormData('username')},saveFormData()带了括号表示将saveFormData函数的返回值undefined作为回调
-
所以,可以在saveFormData中添加函数返回值,将一个函数作为onChange的回调
-
saveFormData就是一个高阶函数
// 第一种:不传参数,将name属性作为key值
saveFormData = (e) => {
const dataType = e.target.name;
this.setState({ [dataType]: e.target.value })
}
<input onChange={this.saveFormData} type="text" name="username" />
//第二种:传参,将一个函数作为回调
saveFormData = (dataType) => {
return (event) => {
this.setState({ [dataType]: event.target.value });
}
}
<input onChange={this.saveFormData('username')} type="text" name="username" />
💕 注意:对象相关的知识
在对象中,dataType是一个变量,调用dataType并解析dataType里面的数据时,要使用[dataType]
💕 总结:
- 高阶函数:如果一个函数符合下面两个规范中的一个,该函数就是高阶函数
- 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
- 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
- 常见的高阶函数:Promise、定时器setTimeout、arr.map
- 函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
function sum(a, b, c) {
return a + b + c
}
function sumNum(a) {
return (b) => {
return (c) => {
return a + b + c
}
}
}
const result = sumNum(1)(2)(3)
console.log(result);
2.4 组件的生命周期
render调用时机:初始化渲染、状态更新之后
componentDidMount调用时机:组件挂载完毕
componentWillUnmount:组件将要卸载
2.4.1 引出生命周期
案例分析:文字自动渐变效果,点击按钮卸载组件
<script type="text/babel">
class Life extends React.Component {
state = { opacity: 1 }
death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
componentDidMount() {
this.timer = setInterval(() => {
let { opacity } = this.state;
opacity -= 0.1;
if (opacity <= 0) opacity = 1;
this.setState({ opacity: opacity })
}, 200)
}
render() {
return (
<div>
<h1 style={{ opacity: this.state.opacity }}>这里是渐变文字效果</h1>
<button onClick={this.death}>点击消失</button>
</div>
)
}
componentWillUnmount() {
clearInterval(this.timer)
}
}
ReactDOM.render(<Life />, document.getElementById('test'))
</script>
-
渐变效果采用设置文字透明度实现,自动渐变要求设置定时器完成,每隔200毫秒透明度降低0.1
-
定时器不能放到render函数中,因为render函数调用1+n次,在初次渲染时调用一次,随后更新数据的次数等于后续调用次数,如果把定时器放到render函数中,就会出现死循环调用的情况
-
将定时器定义到 和render同级的componentDidMount中,组件挂载时调用且只调用一次。
-
将定时改变的透明度设置为标签的透明度
-
点击按钮销毁组件:使用ReactDOM.unmountComponentAtNode函数销毁
-
销毁后发生报错,报错提示如下。组件销毁后定时器仍在工作,所以要在销毁组件之前,清除定时器。将定义好的定时器赋值给timer并挂载到实例上 ,用clearInterval清除定时器。
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
2.4.2 生命周期(旧)
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染 挂载流程:
constructor-componentWillMount-render-componentDidMount
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
-
更新状态里的数据调用this.setState-->shouldComponentUpdate(不写底层默认值为true,相当于控制组件的阀门,如果为false则不继续)-->componentWillUpdate-->render-->componentDidUpdata
-
不想对状态做出修改,只想更新一下:强制更新this.forceUpdate-->跳过阀门不受阀门控制直接进入componentWillUpdate-->render-->componentDidUpdate
-
父组件调用了setState,父组件的状态更改引发父组件的render调用,在父组件的render中写了子组件标签触发,引起在子组件中依次输出componentWillReceiveProps-->shouldComponentUpdate-->componentWillUpdate-->render-->componentDidUpdata
点击换车,改变A组件state中的carName,将更改的车名显示在B组件中(A是父组件、B是子组件)
<script type="text/babel">
class A extends React.Component {
state = { carName: '奔驰' }
changeCar = () => {
let carName = this.state
this.setState({ carName: '奥迪' })
}
render() {
return (
<div>
<h1>我是A组件</h1>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName} />
</div>
)
}
}
class B extends React.Component {
render() {
const { carName } = this.props
return (
<div>我是B组件,我接受到的车是{carName}</div>
)
}
}
ReactDOM.render(<A />, document.getElementById('test'))
</script>
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
componentWillUnmount
💕 常用钩子函数总结:
- 页面一上来就如何---->componentDidMount,一般在这个钩子中做一些初始化的事
- 例如:开启定时器、发送网络请求、订阅消息
- componentWillUnmount做一些收尾工作
- 例如:关闭定时器、取消订阅消息
- render必须使用的一个
——————————————记录于2022.05.16——————————————
2.4.3 生命周期(新)
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
-
constructor()
-
getDerivedStateFromProps横跨挂载和更新
-
render()
-
componentDidMount()
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
-
getDerivedStateFromProps
-
shouldComponentUpdate()
-
render()
-
getSnapshotBeforeUpdate
-
componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()
2.5 虚拟DOM和DOM Diffing算法
2.5.1 重点面试题
1)react/vue 中的key有什么作用?(key的内部原理是什么)
2)为什么遍历列表时,key最好不要用index?
1. 虚拟dom中key的作用
-
简单地说:key是虚拟dom对象的标识,在更新显示时key起着重要作用
-
详细地说:当状态中的数据发生改变时,react会根据新数据生成新的虚拟dom,随后react进行新虚拟dom与旧虚拟dom的diff比较,比较规则如下:
-
旧虚拟dom中找到了与新虚拟dom相同的key:
(1)若虚拟dom中内容没变,直接使用之前的真实dom
(2)若虚拟dom中内容变了,则生成新的真实dom,随后替换掉页面中之前的真实dom
-
旧虚拟dom中未找到与新虚拟dom相同的key
根据数据创建新的真实DOM,随后渲染到页面
注:最小粒度是标签、节点
-
2. 用index作为key可能会引发的问题:
🎈 1.若对数据进行:逆序添加、逆序删除等破坏顺序操作
会产生没有必要的真实DOM更新=>页面效果没问题,但效率低
🎈 2.如果结构中还包含输入类的DOM
会产生错误DOM更新=>界面有问题
🎈 3.注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表用于展示
仅用于渲染列表用于展示,使用index作为key是没有问题的
3. 开发中如何选择key?
-
最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值
-
如果确定知识简单的展示数据,用index也是可以的
——————————————2022.05.17 本章笔记已完成——————————————
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步