【前端学习】React学习笔记-简介、state、props、ref

8 篇文章 0 订阅

跟着尚硅谷的天禹老师学习React
看视频可以直接点击 b站视频地址

1、简介

原生JavaScript的问题

1、原生JavaScript操作DOM繁琐,效率低,可以将DOM操作托管给React。尽管有了jQuery,最后的代码量也是很大的。
2、使用JavaScript直接操作DOM,会触发大量的重绘重排,React采用了虚拟DOM。
3、原生JavaScript没有组件化方案,代码复用率低。

React的特点

1、采用组件化模式,声明式编码,提高开发效率和组件复用率。
2、在React Native中可以使用React语法进行移动端开发。
3、使用虚拟DOM和Diffing算法,减少与真实DOM的交互

虚拟DOM特点

本质是一个js的Object
虚拟DOM比真实DOM要”轻“一点,最终会被React转换成真实DOM

jsx注意事项
  1. 定义虚拟DOM时不要写引号,直接写标签就行
  2. 混入js表达式时用单花括号
  3. class使用className,因为js操作DOM的时候就是用的className,class是当时js的保留字
  4. 关于style使用变量需要双花括号,可以理解成先用单花括号表示这是一个变量,然后传入一个对象,或许也可以传入一个字符串。同时短横杠的属性写法要转成驼峰形式。
const className = 'test'
const VDOM = (
	<div>
		<p className={className} style={{fontSize:'22px'}}>
	</div>
)
  1. jsx只允许有一个根元素,可以参考Vue
  2. 标签必须闭合
  3. 标签如果用小写的标签,会被React自动理解成html的同名标签,找不到就报错;如果是组件就需要写成大写开头,React来渲染这个组件,找不到就报错。甚至还可以写一个中文标签。
  4. 单花括号内只能混入表达式,不能混入语句(比如for循环、if、switch等等)
    • 一个表达式会产生一个值 ,注意函数也可以用表达式来声明
    • 如果需要用if就用三目运算符,如果需要用for就用forEach、map等等
  • 如果要用循环输出一个jsx?(一般来讲不会用index作为key,这里只是简单地试一下)
const frame = ['React','Vue','Angular']
const VDOM = {
	<div>
		frame.map((item,index)=>{
			<p key={index}>{item}</p>
		})
	</div>
}

2、组件实例三大核心属性,state、proprs、refs

模块与组件

模块应该是专指js模块。
组件是实现一个完整功能的代码和资源的集合(比如会包含html、css、js、音视频等等)
模块化:所有js代码都按一定规则抽象。
组件化:各个功能按照一定规则抽象

js类
class Person{
	// 构造方法
	constructor(name,age){
		// this指向new出来的实例对象
		this.name = name;
		this.age = age
	}
	// 一般方法
	speak(){
		// 一般方法在原型上,供实例使用
		// this指向实例对象
		console.log(`my name is ${this.name}`)
	}
}
(new Person('zzy',22)).speak.call({a:1,b:1}) // my name is
class Student extends Person{
	// 即使不写构造器也会直接调用父类的构造方法,当写了子类构造方法,必须明确地调用super
	contructor(name,age,grade){
		super(name,age); // super要置于最顶端
		this.grade = grade;
	}
	// 重载
	speak(){
		console.log(`my name is ${this.name}, im in grade ${this.grade}`)
	}
}
const s1 = new Student('zzy',22,16)
s1.speak() // my name is zzy,im in grade 16
  • 类的构造方法不是必须要写的,除非要对实例属性进行一些初始化
  • 如果子类写了构造方法,那么必须调用super,并且放在最顶端
  • 类中定义的所有方法,都是放在实例上面
组件
  1. 函数式组件
function Demo(){
	console.log(this) // undefined
	return <p>函数式组件</p>
}
React.render(<Demo/>,document.getElementById('someid'))

注意这里的this指向,为undefined,因为开启了严格模式,禁止了this指向window。
另外中文在产物中会被转成unicode码

  • 函数式组件的渲染过程:
    • React解析组件标签Demo,然后找到了Demo
    • 发现是函数式的组件,立即调用这个函数,获取返回值,转为真实DOM。
  1. 类组件(比较适用于复杂组件)
class Demo extends React.component{
	render(){
        return (
            <p>{ 'this is demo'}</p>
        )
	}
}
ReactDOM.render(<Demo/>,document.getElementById('id'))
  • 必须写render并有返回值
  • 函数式组件的渲染过程:
    • React解析组件标签Demo,然后找到了Demo
    • 发现是类定义的组件,立即调用new出一个实例,并通过这个实例调用render函数。
    • 将render函数返回的虚拟DOM渲染成真实DOM

注意组件的三个属性,refs、state、props,这些在React.component中已经被定义过。
Demo也称组件实例对象。
关于简单组件和复杂组件,有状态的就是复杂组件,适用类组件,没有状态的就是简单组件,适用函数组件。虽然hooks也提供了函数组件保存状态的能力,还是推荐按照上面的建议来定义组件。
React通过组件的状态驱动视图。

state
初始化state
<div id="container">
</div>
<script>
// 创建组件
class Weather extends React.component{
    constructor(props){
        super(props)
        // 初始化状态
        this.state = {
            isHot:false
        }
    }
    render(){
        return (
            <h1>今天天气很{this.isHot?'热':'冷'}</h1>
        )
    }
}
ReactDOM.render(<Weather/>,document.getElementById('container'))
</script>

js中绑定事件处理函数
<div>
    <button id='btn1'>
    <button id='btn2'>
    <button id='btn3' onclick="test()">
</div>
<script>
    const btn1 = document.getElementById('btn1')
    const btn2 = document.getElementById('btn2')
    const test = function(){
        alert('click')
    }
    btn1.addEventListener('click',test)
    btn2.onclick = ()=>{test()}
</script>

注意React将所有的绑定的事件名都给封装了,比如onclick应该用onClick来写。

<div id="container">

</div>
<script>
	// 创建组件
class Weather extends React.component{
    constructor(props){
        super(props)
        // 初始化状态
        this.state = {
            isHot:false
        }
    }
    render(){
    	const { isHot } = this.state
        return (
            // 注意这里一定不要写成带括号的,不然会将clickHandler的返回值绑定到onClick上
            <h1 onClick={this.clickHandler}>今天天气很{isHot ?'热':'冷'}</h1>
        )
    }
    clickHandler(){
        this.state.isHot = !this.state.isHot;
    }
}
ReactDOM.render(<Weather/>,document.getElementById('container'))

</script>

书写上述代码时会发现,控制台报错了,这是因为等到事件处理时,相当于一个普通的函数,不会被,可以参考下面的两段代码

class Person{
	// 构造方法
	constructor(name,age){
		// this指向new出来的实例对象
		this.name = name;
		this.age = age
	}
	// 一般方法
	speak(){
		// 一般方法在原型上,供实例使用
		// this指向实例对象
		console.log(`my name is ${this.name}`)
	}
}

const p1 = new Person('tom',12)
p1.speak() // my name is tom
speak = p1.speak
// speak() // Cannot read properties of undefined (reading 'name')
// 这是因为class中代码,会自动将转为严格模式,防止this指向window
const f1 = function(){
    console.log(this)
}
const f2 = function(){
    'use strict'
    console.log(this)
}
f1() // window
f2() // undefined
  • 定义事件处理函数时,将其放在原型对象上(类里面),供实例使用
  • 由于事件处理函数是作为onClick的回调,所以不是通过实例直接调用的,是直接调用
  • 类内部默认开始了局部的严格模式,所以事件处理函数中的this是undefined
解决类中this指向的问题

比如上面可以改成下面的代码,使用bind改变this指向并返回新的函数。

 constructor(props){
        super(props)
        // 初始化状态
        this.state = {
            isHot:false
        }
        // 解决clickHandler中this指向问题
        this.clickHandler = this.clickHandler.bind(this)
    }

相当于实例上加了这么一个方法,注意在clickHandler依然需要在类内声明。

setState

state是不可以被直接赋值的,需要借助setState。
修改clickHandler代码:

clickHandler(){
    //this.state.isHot = !this.state.isHot;
	this.setState({
		isHot:!isHot   	
	})
    }

注意这里setState是一个更新/合并操作,并不是整体的赋值
这里的construcor只调用了一次,而不是每次setState都要重新new,而render会除了初始化的那一次,只要setState修改了状态,render就会重新被调用。

state的简写方法

实例对象的属性的简写

class Car{
    constructor(name,price){
        this.name = name
        this.price = price
        this.wheel = 4
    }
    // 类中可以写赋值语句,下方代码就是给实例添加一个属性a
    a = 1

}
const c1 = new Car('宾利',299)
const c2 = new Car('宝马',199)

下面的代码才是开发中会常用的形式

<div id="container"></div>
<script>
class Weather extends React.component{
    // 直接在这里声明state即可,不用写在构造函数中
    state = {
            isHot:false
    }
    constructor(props){
        super(props)
        // 初始化状态
        
    }
    render(){
        const { isHot } = this.state
        return (
            // 注意这里一定不要写成带括号的,不然会将clickHandler的返回值绑定到onClick上
            <h1 onClick={this.clickHandler}>今天天气很{isHot?'热':'冷'}</h1>
        )
    }
    // 自定义方法写成用赋值语句+箭头函数的形式
    // 这里的自定义方法一定要写成箭头函数
    clickHandler = ()=>{
        const isHot = this.state.isHot
        this.setState({
            isHot:!isHot
        })
        console.log(this) // weather实例
    }
}
ReactDOM.render(<Weather/>,document.getElementById('container'))
</script>

理解State

1、组件可以被理解为“状态机”,通过更新组件的State来更新对应页面的显示(重新渲染组件)
2、组件中render方法中的this为组件的实例对象
3、组件自定义方法中this为undefined,如何解决?

  • 强制绑定this,通过bind方法返回一个新的具有this指向的方法
  • 箭头函数(以及赋值语句,也是最常用的形式)
    4、状态(state)数据不能直接修改或者更新
Props
简介
<div id="container"></div>
<script>
	class Person extends React.Component{
        render(){
            const {name,sex,age}
            return (
                <ul>
                    <li>{name}</li>
                    <li>{sex}</li>
                    <li>{age}</li>
                </ul>
            )
        }
    }
    const person = {
        name:'1',
        age:'1',
        sex:'1'
    }
    // 下面两种方式是等价的
    ReactDOM.render(<Person name={person.name} age={person.age} sex={person.sex}/>),document.getElementById('container')    
    ReactDOM.render(<Person {...person}/>),document.getElementById('container')
</script>

注意"…"运算符

const person = {
        name:'1',
        age:'1',
        sex:'1'
}
// 构造字面量时使用展开语法
const person2 = {...person}
const person3 = {...person,name:"3"} // 合并操作
console.log(...person) // 报错
console.log(person2) // person的复制

补充,这里的"…"运算符并非数组的扩展运算符,而是使用对象字面量复制一个对象。详见展开运算符。
但是要注意,React中的这段代码,单花括号是指在此处使用js表达式,而非上面的复制对象,是使用reactbabel来编译过的。

ReactDOM.render(<Person {...person}/>),document.getElementById('container')
对Props进行限制

注意,向标签种传入的值,都被默认成字符串,像下面的age就会被认为是字符串19,如果在一些操作的里边,使用"+"运算符,会被认为成字符串运算

ReactDOM.render(<Person age="
19" name="1" sex="1"/>),document.getElementById('container')

所以应当改成下面的代码

ReactDOM.render(<Person age={19}
/>),document.getElementById('container')

当别人复用我们的组件时,不知道如何使用,这就要加一些限制。在Person类同级目录下中添加下面的代码。

// 需要引入 prop-types库
// 在React16版本后单独分出一个prop-types库
// 之所以修改是因为,React官方不希望React库变得太大,并且一般组件也有可能不需要限制Props
Person.propTypes = {
	// 注意这里都是需要写成小写,为了和内置类型区分
	name:React.propTypes.string.isRequired, // 必填且为字符串	
	sex:React.propTypes.string,
	sex:React.propTypes.number, 
	speak:propTypes.func, // func是为了和function关键字区分
}
// 赋默认值
Person.defaultProps = {
	sex:"unknown",
	age:18,
	name:"unknown"
}
Props的简写方式

注意props是禁止修改的,这里好像和Vue是一样的。
要把props加入到类的静态属性中(static

Person extends React.Component{
	state = {  }
	render(){  }
	static propTypes = {
		// 注意这里都是需要写成小写,为了和内置类型区分
		name:React.propTypes.string.isRequired, // 必填且为字符串	
		sex:React.propTypes.string,
		sex:React.propTypes.number, 
		speak:propTypes.func, // func是为了和function关键字区分
	}
	static defaultProps = {
		sex:"unknown",
		age:18,
		name:"unknown"
	}
}
类式组件中的构造函数

通常,在React中,构造函数仅用于以下两种i情况:

  • 通过给 this.state 赋值对象来初始化内部state
  • 为事件处理函数绑定实例

在React组件挂载之前,会调用它的构造函数。在为React.Compnent子类实现构造函数时,应在其他域据之前调用super(props),否则,this.proprs在构造函数中可能会出现未定义的bug。
总结:类中的构造函数完全可以省略,但是如果写了构造函数,并且在构造函数的声明中写了props,那就需要在super中传入props。也就是说,构造函数是否接受过props,是否传给super,取决于是否希望在构造函数中通过this访问props。
但既然传入了props,也没有必要使用this来访问props。
这样来看,class中的构造函数写不写都行。

函数式组件使用props

函数式组件因为没有this,所以不可以访问state,refs,但是可以接收props,通过传参来实现。

function Person(props) {
    const { name, age, sex }
    return (
        <h1>
            <p>{name}</p>
            <p>{age}</p>
            <p>{sex}</p>
        </h1>
    )
}
// 函数式组件只能这样来限制props
Person.defaultProps = {
    sex: "unknown",
    age: 18,
    name: "unknown"
}
Person.propTypes = {
    // 注意这里都是需要写成小写,为了和内置类型区分
    name: React.propTypes.string.isRequired, // 必填且为字符串	
    sex: React.propTypes.string,
    sex: React.propTypes.number,
    speak: propTypes.func, // func是为了和function关键字区分
}
理解Props
  • 一定要写好propTypes,如果写成小写t,也不会报错,但是效果就没有了,defaultProps同理。
  • 每个组件对象都会有props属性,保存了通过标签传入的属性。
  • 组件标签的所有属性都保存在props上
  • 在组件内一定不要修改props数据
  • props的作用是从组件外部向组件内传递变化的数据
refs的基本使用
字符串形式的ref:

实现点击按钮弹窗和input框失焦弹窗

<div id="container"></div>
<script>
	class Demo extends React.Component{
    showData = () => {
        const { input1 } = this.refs
        alert(input1.value) // 注意这里拿到的是真实的DOM
    }
    showData2 = () => {
        const { input2 } = this.refs
        alert(input2.value)
    }
    render(){
        return (
            <div>
                <input ref="input1" type="text" placeholder="点击按钮"/>
                <button ref="btn" onClick={this.showData}>点击</button>
                <input onBlur={this.showData2} ref="input2" type="text" placeholder="失去焦点"/>
            </div>
        )
    }
}
React.DOM(<Demo />,docuemnt.getElementById('container'))
</script>
回调形式的ref:过时的API

React官方未来可能会废弃掉上面的字符串形式的ref,可能是因为字符串形式的ref效率不高。一般会考虑使用回调函数形式或者createRef这个API

<div id="container"></div>
<script>
	class Demo extends React.Component{
    showData = () => {
        const { input1 } = this // 注意这里修改了,不是原来的this.refs
        alert(input1.value)
    }
    showData2 = () => { 
        const { input2 } = this  // 注意这里修改了,不是原来的this.refs
        alert(input2.value)
    }
    render(){
        return (
            <div>
                <input ref={currentNode => this.input1 = currentNode } type="text" placeholder="点击按钮"/>
                <button ref="btn" onClick={this.showData}>点击</button>
                <input ref={currentNode => this.input2 = currentNode } onBlur={this.showData2} type="text" placeholder="失去焦点"/>
            </div>
        )
    }
}
React.DOM(<Demo />,docuemnt.getElementById('container'))

</script>

关于这里的ref回调函数中的this,因为写的是箭头函数,所以this和render中的this相同,render的this实际指向了实例本身。
回调函数中直接在实例内部声明了input1、input2,所以在事件处理函数中直接用this.input1、this.input2来拿数据。

回调函数执行次数问题

如果ref回调函数(就是上面的写法)是用内联函数形式定义的,更新过程中它会被执行两次。这是因为,在每次渲染时会创建一个新的函数实例,所以React清空旧的ref并且设置新的。通过将ref的回调函数定义成class绑定的函数的方式,可以避免上述问题,但大多数情况下它是无关紧要的。
第一次传入了null,这是为了确认原来的旧值能够确认被清空;第二次传入了节点信息。
试一下"class的绑定函数":

saveInput = (currentNode) => {
        this.input1 = currentNode
    }
    render(){
        return (
            <div>
                <input ref={this.saveInput} type="text" placeholder="点击按钮"/>
                <button ref="btn" onClick={this.showData}>点击</button>
            </div>
        )
    }

补充:如何在jsx中写注释:

render(){
    return (
        <div>
            { // 加上花括号以写注释 }
        </div>
    )
}

这里只是来看一下class绑定函数,实际开发最好还是写成内联的,不然逻辑就更多了。

createRef API : 最推荐的形式

React.createRef调用后可以返回一个容器,这个容器可以存储被ref所标识的节点
可以理解成,当input的ref传入了myRef,这个input节点会被存入React专门存放节点的容器中
但是要注意,这个容器最好只保存一个节点,“专人专用”,不然就被其他节点给占用

<div id="container"></div>
<script>
	class Demo extends React.Component{
    /*
        React.createRef调用后可以返回一个容器,这个容器可以存储被ref所标识的节点
        可以理解成,当input的ref传入了myRef,这个input节点会被存入React专门存放节点的容器中
        但是要注意,这个容器最好只保存一个节点,“专人专用”,不然就被其他节点给占用
    */
    myRef = React.createRef()
    showData = () => {
        alert(this.myRef.current.value) // 注意这里有改动
    }
    }
    render(){
        return (
            <div>
                <input ref={ this.myRef } type="text" placeholder="点击按钮"/>
                <button ref="btn" onClick={this.showData}>点击</button>
            </div>
        )
    }
}
React.DOM(<Demo />,docuemnt.getElementById('container'))
</script>

理解ref
  • 字符串形式的尽量避免但老代码中肯定很常见,经常用的是回调形式并且内联的很方便,createRef API被官方推荐
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值