<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新增的构造函数-class关键字
在react中 类组件必须继承于React.Component这个组件(React.Component组件中存在生命周期函数等方法)
import React from 'react'
class 组件名 extends React.Component{
render(){
return vodom
}
}
[1]查看组件首字母大/小写:
若是首字母小写则默认为html标签,会去寻找对应的html标签并渲染,若是没有找到对应的html标签则会报错;
若是首字母大写则默认为组件,会去寻找对应的组件并渲染,若是没有找到对应的组件就会报错;
tips: 组件的组件名
首字母
必须大写
,否则会被认作html标签
[2]开始渲染(此处以类组件为例)
(1)通过new关键字去实例化对象-> 在通过new关键字去实例化对象时会自动调用constructor构造器创建实例化对象并修改this指向;
(2)在获取到实例化对象之后,react会通过实例化对象调用render函数获取vdom并渲染;
tips: 若是想看详细渲染、更新、销毁过程可见类组件的生命周期
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必须是一个对象
!
若需要声明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中的数据,虽然数据变化了,但是却不会重新渲染数据
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的。
使用时机-什么时候使用对象/函数呢?
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}))
}
}
对象式的setState是函数式的setState的语法糖注意点
[1] 使用setState去修改数据是替换还是合并?
不是替换而是合并
直接通过点语法去获取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}))
}
}
和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;
同样的,我们可以约定规范 如传递数据的数据类型、是否可以为空、设置默认值等。
在 React v15.5 版本之前,在React上存在PropTypes函数用于进行props属性的类型交验。
从 React v15.5 开始 ,React.PropTypes 助手函数已被弃用,更推荐使用 prop-types
库 来定义contextTypes。
使用语法如下:
npm i prop-types
import PropTypes from 'prop-types'
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 = {
属性值: 规则限制
}
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;
此时name属性、sex属性赋值没有问题,但是age属性的赋值却不符合要求,因此在控制台会给出警告(但是不会报错),如下: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>
)
}
}
在react中尽量不要直接操作dom,如果要操作dom,可以使用refs;
定义ref的形式有3种
字符串形式的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,则会在控制台提示如下
在了解回调形式的ref之前需要先了解什么是回调?
什么是回调形式的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。
原来这个问题产生的根本原因是将回调函数写在了行内
在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>
)
}
}
生命周期