目录
- 复习
- 展开运算符
- 组件
- 什么叫做组件?
- 分类
- 类组件
- es6新增构造函数
- 语法
- 类组件渲染
- 类组件的this指向
- 类组件的三大属性
- state
- 作用
- 语法-初始化数据
- 语法-修改state数据
- 语法-获取state中的数据
- 案例
- props
- propTypes属性(prop-types库)
- defaultProps属性
- refs
- [1] 字符串形式的ref
- [2] 回调形式的ref
- createRef形式的ref
- 类组件的生命周期
复习
展开运算符
<script>
let arr1 = [1, 3, 5, 7, 9]
let arr2 = [2, 4, 6, 8, 10]
// 【1】展开一个数组
console.log(...arr1);
// 【2】合并数组
let arr3 = [...arr1, ...arr2]
// 【3】在函数接收参数使用中使用
function sum(...numbers) {
return numbers.reduce((preValue, currentValue) => {
return preValue + currentValue
})
}
console.log(sum(1, 2, 3, 4));
// 【4】构造字面量对象时使用展开语法
let person = {name: 'tom', age: 18}
let person2 = {...person}
// 【5】合并修改
let person3 = {...person, name: 'jack', address: "地球"}
console.log(person3)// {address: "地球", age: 18, name: "jack"}
// 注意:展开运算符不能展开对象
//console.log(...person); //报错,
</script>
总结:
展开运算符可以用来展开数组、合并数组、构造字面量对象时使用展开语法、合并修改,但是却不可以展开对象
!
组件
什么叫做组件?
用来实现局部功能效果的代码和资源的集合叫做组件(html/css/js/image等)
分类
在react中组件分为函数式
组件与类式
组件;
今天主要了解的是类式组件~
类组件
es6新增构造函数
es6新增的构造函数-class关键字
语法
在react中 类组件必须继承于React.Component这个组件(React.Component组件中存在生命周期函数等方法)
import React from 'react'
class 组件名 extends React.Component{
render(){
return vodom
}
}
- [1] 每一个类组件强制存在一个render函数,这个函数的返回值为jsx元素(vdom)
- [2] vdom必须存在一个根标签,最外层多个标签并列是不合法的;
- [3] 在vdom中可以插入js表达式,表达式返回的结果会相应的渲染到页面上面
类组件渲染
-
[1]查看组件首字母大/小写:
若是首字母小写则默认为html标签,会去寻找对应的html标签并渲染,若是没有找到对应的html标签则会报错;
若是首字母大写则默认为组件,会去寻找对应的组件并渲染,若是没有找到对应的组件就会报错;
tips: 组件的组件名
首字母
必须大写
,否则会被认作html标签 -
[2]开始渲染(此处以类组件为例)
(1)通过new关键字去实例化对象-> 在通过new关键字去实例化对象时会自动调用constructor构造器创建实例化对象并修改this指向;
(2)在获取到实例化对象之后,react会通过实例化对象调用render函数获取vdom并渲染;tips: 若是想看详细渲染、更新、销毁过程可见类组件的生命周期
类组件的this指向
this指向的原则是:
谁调用指向谁
接下来我将尽可能的列举出所有类组件中方法调用,并说明方法的this指向
import React from 'react'
class App extends React.Component{
render(){
// 在渲染过程中也讲述过,render函数是获取到实例化对象之后由实例化对象调用的,因此this指向的是实例化对象
console.log('render-this', this)
return (
<div>
<p onClick={this.say}>111</p>
<p onClick={this.say2}>222</p>
</div>
)
}
say(){
// 该方法的调用是绑定在vdom上;
// 在渲染-获取vdom时若是发现vdom上存在事件绑定则会执行被绑定的代码得到的值在事件被触发时直接调用/执行,因此this指向undefined(严格模式下)
console.log('恭喜发财', this)
}
say2 = ()=>{
// 该函数为箭头函数,需要通过作用域链寻找this,因此this指向实例化对象
console.log('恭喜发财*2', this)
this.say3()
}
say3(){
// 该函数是实例化对象直接调用,因此this指向实例化对象
console.log('恭喜发财*3', this)
}
}
export default App;
类组件的三大属性
react的三大属性其实是实例化对象的三大属性,由于函数组件是没有实例化的,因此将这三大属性称为类组件的三大属性。
state
作用
用于存储数据,状态(数据)驱动试图
语法-初始化数据
state必须是一个对象
!
-
若需要声明constructor函数则可以将state初始化写在constructor中
class App extends React.Component{ constructor(){ super() this.state={ ishot:true } } render(){ return ( <div> <p>今天天气{this.state.ishot?'炎热':'凉爽'}</p> </div> ) } }
此时页面显示‘今天天气炎热’
-
若是不需要声明constructor函数则可以直接将state初始化在类中
class App extends React.Component{ state={ ishot:true } render(){ return ( <div> <p>今天天气{this.state.ishot?'炎热':'凉爽'}</p> </div> ) } }
语法-修改state数据
若是直接通过点语法去修改state中的数据,虽然数据变化了,但是却不会重新渲染数据
class App extends React.Component{
state={
name:'chaochao',
ishot:true
}
render(){
return (
<div>
<p>今天天气{this.state.ishot?'炎热':'凉爽'}</p>
<p onClick={this.editstate}>改变状态</p>
</div>
)
}
editstate = ()=>{
this.state.ishot = !this.state.ishot
console.log('state', this.state.ishot) // false、true、false...
}
}
可以发现,虽然每次打印ishot的值发生了变化,但是页面显示的文本一直是‘今天天气炎热’不变。
在react中可以用过setstate
函数去修改state中的值,修改方式如下
语法
setState(Object/Function,[callback])
-
方式1
setState({属性名:属性值})
-
方式2
setState((state,props)=>{ return {属性名: 属性值} })
若是第一个参数为函数:当调用setState函数时,react会自动调用该函数,并将当前state与props传入,因此在该函数中是可以获取当前实例对象的state 与props的。
使用时机-什么时候使用对象/函数呢?
- 若是新状态不依赖于原状态 -> 推荐使用对象式的state
- 若是新状态依赖于原状态 -> 推荐使用函数式的state(因为这样不需要获取state中的数据了)
- 举例说明:增加count的值需要依赖于当前state推荐使用函数
对象式的setState是函数式的setState的语法糖class App extends React.Component{ state={ name:'chaochao', ishot:true, count:0 } render(){ return ( <div> <p>{this.state.count}</p> <p onClick={this.editstate}>改变状态</p> </div> ) } editstate = ()=>{ // this.setState({count: ++this.state.count}) this.setState(state=> ({count: ++state.count})) } }
注意点
[1] 使用setState去修改数据是替换还是合并?
- 使用内置API setState去修改state中的数据,这个数据的修改
不是替换而是合并
- 举例说明
- 在初始化时数据state = {isHot:false, xxx:111} 有两条数据
- 当通过this.setState({isHot:true})去修改数据
- 再次获取数据时 {isHot:true, xxx:111} xxx不会被覆盖
语法-获取state中的数据
直接通过点语法去获取state中的数据即可。需要注意的是setstate修改state中的数据是异步的
,因此获取state中数据时也要注意时机。
举例说明
class App extends React.Component{
state={
name:'chaochao',
ishot:true,
count:0
}
render(){
return (
<div>
<p>{this.state.count}</p>
<p onClick={this.editstate}>改变状态</p>
</div>
)
}
editstate = ()=>{
this.setState(state=> ({count: state.count+1}))
console.log('state', this.state.count)
}
}
上述案例中 在第一次点击时 页面显示的数值是1,而打印的还是之前的值0。
这是因为setState更新数据是异步的,若是想要在通过setState修改数据后,立即拿到修改后的数据,可以使用setState的第二个参数
this.setState(stateChange,[callback])
第二个参数是一个回调函数,该回调函数的执行时机是在 在此次数据更新的render函数调用之后执行 : setState->render -> callback
editstate = ()=>{
this.setState(state=> ({count: state.count+1}),()=>{
console.log('state', this.state.count)
})
}
此时第一次点击,控制台打印的就是1了
案例
案例:默认显示文本 今天天气炎热,当点击文本时显示 今天天气凉爽
class App extends React.Component{
state={
ishot:true
}
render(){
const {ishot} = this.state
return (
<div>
<p onClick={this.editstate}>今天天气{ishot?'炎热':'凉爽'}</p>
</div>
)
}
editstate = ()=>{
this.setState(state=> ({ishot: !state.ishot}))
}
}
props
和vue相同,props在react中的作用也是进行父子传值。
- 在父组件中: 将需要传递给子组件的数据以 属性 的形式添加在子组件标签上
<子组件 属性名1=属性值1 属性名2=属性值2 .../>
- 在子组件中:通过
实例化对象的props属性
进行接收this.props // 将接收到props这个对象中
示例如下:
import React from 'react'
class People extends React.Component{
render(){
console.log('props', this.props) // {name: 'chaochao', age: 18, area: '中国'}
return 111
}
}
class App extends React.Component{
render(){
return (
<div>
<People name='chaochao' age={18} area='中国'/>
</div>
)
}
}
export default App;
同样的,我们可以约定规范 如传递数据的数据类型、是否可以为空、设置默认值等。
propTypes属性(prop-types库)
在 React v15.5 版本之前,在React上存在PropTypes函数用于进行props属性的类型交验。
从 React v15.5 开始 ,React.PropTypes 助手函数已被弃用,更推荐使用 prop-types
库 来定义contextTypes。
使用语法如下:
- 下载prop-types库
npm i prop-types
- 引入prop-types库
import PropTypes from 'prop-types'
- 给类组件添加propTypes属性进行属性限制,限制规则如下
PropTypes.number // 数据为数字类型 PropTypes.string // 数字为字符串类型 PropTypes.bool // 数据为boolean值 PropTypes.symbol // 数据为symbol值 PropTypes.array // 数据为数组类型 PropTypes.func // 数据为函数类型 PropTypes.element // 数据为React元素 PropTypes.instanceOf(Message) //prop 是类的一个实例 PropTypes.oneOf(['News', 'Photos']) // prop是一个枚举 PropTypes.oneOfType([ PropTypes.string, PropTypes.number, PropTypes.instanceOf(Message) ]) // prop是多种类型之一 PropTypes.func.isRequired // prop是函数类型且必填 PropTypes.any.isRequired // prop可以是任意类型但必填
- 语法如下:
static propTypes = { 属性值: 规则限制 }
Component.propTypes = { 属性值: 规则限制 }
- 举例说明
此时name属性、sex属性赋值没有问题,但是age属性的赋值却不符合要求,因此在控制台会给出警告(但是不会报错),如下:import React from 'react' import PropTypes from 'prop-types' import './App.css'; // 子组件 - 希望name属性值为字符串类型且必填,sex属性为字符串类型默认值为‘weizhi’(默认值后面讲解defaultProps属性时再进行设置), age属性值为数字类型 class People extends React.Component{ static propTypes = { name: PropTypes.string.isRequired, sex: PropTypes.string, age: PropTypes.number } render(){ // console.log('props', this.props) return ( <> son </> ) } } class App extends React.Component{ render(){ return ( <div> <People name='chaochao' sex='nv' age='18'/> </div> ) } } export default App;
defaultProps属性
propTypes属性用于规范prop的类型,defaultProps属性用于设置属性默认值,语法如下:
static propTypes = {
属性值: 规则限制
}
Component.propTypes = {
属性值: 规则限制
}
上面案例为sex添加默认值如下
import React from 'react'
import PropTypes from 'prop-types'
import './App.css';
// 子组件- 希望name属性值为字符串类型且必填,sex属性为字符串类型默认值为‘weizhi’, age属性值为数字类型
class People extends React.Component{
static propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number
}
static defaultProps = {
sex:'weizhi'
}
render(){
console.log('props', this.props) // {name: 'chaochao', age: 18, sex: 'weizhi'}
return (
<>
son
</>
)
}
}
class App extends React.Component{
render(){
return (
<div>
<People name='chaochao' age={18}/>
</div>
)
}
}
refs
在react中尽量不要直接操作dom,如果要操作dom,可以使用refs;
定义ref的形式有3种
[1] 字符串形式的ref
字符串形式的ref与Vue中ref的使用相同~
每个类实例化对象身上都存在一个refs
属性,该属性为一个对象。
若是想要获取某个dom,则可以给该vdom上添加ref属性-属性值为字符串。添加之后就会将该dom以键值队的形式添加到refs对象中。
若是没有给任何vdom添加ref属性,则该实例化对象的refs属性为一个空对象。
获取dom元素需要等待dom元素渲染完毕,在render里面是获取不到dom元素的,如下:
class App extends React.Component{
render(){
console.log('refs', this.refs) // {}
return (
<div ref='box'></div>
)
}
}
在渲染完毕之后就可以正常获取dom元素了
class App extends React.Component{
render(){
return (
<div ref='box'>
<button onClick={this.getValue}>点我显示数据</button>
</div>
)
}
getValue=()=>{
console.log('refs', this.refs) // refs {box: div}
}
}
需要注意的是该形式在官方不推荐使用,因为使用此方式定义ref若是多次定义—效率有很大的问题。
若是使用字符串形式获取dom,则会在控制台提示如下
[2] 回调形式的ref
在了解回调形式的ref之前需要先了解什么是回调?
- [1] 定义了一个函数
- [2] 没有主动调用函数
- [3] 函数最终执行了
什么是回调形式的ref呢?
若是想获取dom元素,就在vdom上添加ref属性只不过属性值是一个函数。
在渲染vdom时,若是发现ref属性值是一个函数,react会调用这个函数并且将该dom作为参数传入。
class App extends React.Component{
render(){
return (
<div ref={dom => {
console.log('dom', dom)
this.box = dom // 获取到dom元素进行赋值
}}>
<button onClick={this.getValue}>点我显示数据</button>
</div>
)
}
getValue=()=>{
console.log('box', this.box) // dom元素
}
}
但是像以上这种将回调函数写在行内
的会存在一个问题:更新时重新调用render函数时会重新调用该回调函数两次
。
class App extends React.Component{
state = {
hot: true
}
render(){
return (
<div ref={dom => {
console.log('dom', dom)
this.box = dom // 获取到dom元素进行赋值
}}>
<button onClick={()=>{
this.setState({hot: false})
}}>点我修改hot</button>
</div>
)
}
}
在上述案例中,当我点击“点我修改hot”时,发现回调函数执行了两次结果如下:
第一次获取的dom是个空值,第二次获取真实dom。
原来这个问题产生的根本原因是将回调函数写在了行内
- 当将函数写在行内时
- 渲染-> 执行render函数时-> 发现ref绑定的是一个函数,会自动调用该函数并将该dom元素作为参数传入;
- 更新 ->重新调用render函数,此次执行过程中会调用2次该函数—第一次传入的参数为null,第二次传入的才是dom
没有造成实质的影响,因为这两次调用过程很短暂;
- 不将回调函数写在行内
- 渲染-> 执行render函数时-> 发现ref绑定的是一个函数,会自动调用该函数并将该dom元素作为参数传入;
- 更新 -> 不调用此函数;
createRef形式的ref
在React上存在createRef
方法,该方法被调用后会返回一个对象
{
current:...
}
该容器可以存储ref所标识的节点
(存储在current属性中),该容器是专人专用的(一个容器仅能存储一个dom)
this.input = React.createRef() // 产生一个容器
<input ref={ this.input }> // 存储
const {current} = this.input // 容器的current属性表示当前的dom元素
举例说明
class App extends React.Component{
box = React.createRef()
render(){
return (
<div ref={this.box}>
<button onClick={()=>{
console.log('box', this.box.current)
}}>点我修改hot</button>
</div>
)
}
}
类组件的生命周期
生命周期