React学习02-React面向组件编程

news2024/11/25 0:28:34

React 开发者工具

推荐使用Chrome或Edge浏览器,安装React Developer Tools(Facebook出品)。

image-20220213160959897

安装完成后,访问使用React编写的页面时,图标会高亮(开发环境为红色有debug标识,生产环境为蓝色),同时F12开发者工具中会多出ComponentsProfiler两个选项卡。

React组件

函数式组件

效果

image-20220213181322649

简单组件:无状态state

代码

// 1.创建函数式组件
function Demo() { // 创建组件,函数名首字母须大写
    console.log(this) // 此处的this是undefined,因为babel编译后开启了严格模式
    return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
// 2.渲染组件到页面
ReactDOM.render(<Demo/>, document.getElementById('test'))

执行ReactDOM.render(<Demo/>, document.getElementById('test'))后发生了什么?

  1. React解析组件标签,找到了Demo组件

  2. 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中

类式组件

JavaScript类

构造
// 创建一个Person类
class Person {
// 构造器方法
constructor(name, age) {
// 构造器中的this:类的实例对象
this.name = name
this.age = age
}
// 一般方法
speak() {
// speak()方法放在类的原型对象上,供实例使用
// 通过Person实例调用speak时,speak中的this就是Person实例
// 谁调用它,它指向谁
console.log(`我叫${this.name},我的年龄是${this.age}`)
}
}

// 创建一个Person的实例对象
const p1 = new Person('tom', 20)
const p2 = new Person('jerry', 22)

console.log(p1)
console.log(p2)

p1.speak()
p2.speak()

image-20220213192510026

继承
// 创建一个Student类,继承于Person类
class Student extends Person {
// 无新增属性可不写构造器方法
constructor(name, age, grade) {
super(name, age)
this.grade = grade
this.school = 'xxxx'
}
// 重写从父类继承的方法
speak() {
console.log(`我叫${this.name},我的年龄是${this.age},我今年读${this.grade}`)
}
study() {
// study()方法放在类的原型对象上,供实例使用
// 通过Student实例调用study时,study中的this就是Student实例
console.log('我每天努力学习')
}
}

// 创建一个Student的实例对象
const s1 = new Student('小王', 15)
const s2 = new Student('小张', 16, '高一')

console.log(s1)
console.log(s2)

s2.speak()

image-20220213192709261

  1. 类中的构造器不是必须写的,要对实例进行一些初始化的操作(如添加指定属性)时才写;
  2. 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的;
  3. 类中所定义的方法,都是放在了类的原型对象上,供实例去使用

类中可以直接写赋值语句。

类中this的指向
class Person {
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    speak() {
        console.log(this)
    }
}

const p1 = new Person('tom', 18)
p1.speak()
const x = p1.speak
x()

image-20220218142424764

效果

屏幕截图 2022-02-13 194815

复杂组件:有状态state

代码

// 1.创建类式组件
class Demo extends React.Component {
    render() {
        // render方法放在Demo的原型对象上,供实例使用
        // render中的this:Demo的实例对象
        console.log('render中的this: ', this)
        return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
    }
}
// 2.渲染组件到页面
ReactDOM.render(<Demo/>, document.getElementById('test'))

执行ReactDOM.render(<Demo/>, document.getElementById('test'))后发生了什么?

  1. React解析组件标签,找到了Demo组件
  2. 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法
  3. 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中

简单组件与复杂组件的区别

简单组件:无状态 state

复杂组件:有状态 state

组件实例的三大核心属性

1.state

原生js中的事件绑定

<body>
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<button onclick="demo()">按钮3</button>

<script>
  const btn1 = document.getElementById('btn1')
  btn1.addEventListener('click', ()=>{
      alert('点击按钮1')
  }) // 兼容性最好

  const btn2 = document.getElementById('btn2')
  btn2.onclick = ()=>{
      alert('点击按钮2')
  }

  function demo() {
      alert('点击按钮3')
  }
</script>
</body>

React中的事件绑定

class Weather extends React.Component {
constructor(props) {
  super(props)
  // 初始化状态
  this.state = {isHot: true}
}
render() {
  console.log(this)
  // 读取状态
  const {isHot} = this.state
  return <h1 onClick={demo}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
}
}

function demo() {
console.log('标题被点击')
}

注意return <h1 onClick={demo}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>中html用法要改为驼峰,demo函数不需要加括号

类内的方法作为回调时,不通过实例调用

class Weather extends React.Component {
    constructor(props) {
        super(props)
        // 初始化状态
        this.state = {isHot: true}
    }
    render() {
        // console.log(this)
        // 读取状态
        const {isHot} = this.state
        return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
    }
    changeWeather() {
        // changeWeather放在Weather的原型对象上,供实例使用
        // 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
        // 类中的方法默认开启了局部的严格模式,所以changeWeather中的this是undefined
        console.log(this)
    }
}

image-20220218143656468

解决方法:在构造器中绑定

this.changeWeather = this.changeWeather.bind(this)

image-20220218143845258

image-20220218144055873

JavaScript中的bind:

function demo() {
console.log(this)
}
demo()
const x = demo.bind({a:1, b:'b'})
x()

image-20220218144700014

效果

GIF 2022-2-19 12-55-13

代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>三大属性 - state</title>
</head>
<body>
    <div id="test"></div>

    <!-- 引入 React 核心库 -->
    <script type="text/javascript" src="../../js/react.development.js"></script>

    <!-- 引入 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 */

        // 1.创建组件
        class Weather extends React.Component {
            // 构造器调用——1次
            constructor(props) {
                super(props)
                // 初始化状态
                this.state = {isHot: true}
                // 绑定changeWeather中的this指向
                this.changeWeather = this.changeWeather.bind(this)
            }
            // render调用——1+n次(初始化+状态更新次数)
            render() {
                // console.log(this)
                // 读取状态
                const {isHot} = this.state
                return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
            }
            // changeWeather调用次数与点击次数相同
            changeWeather() {
                // changeWeather放在Weather的原型对象上,供实例使用
                // 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
                // 类中的方法默认开启了局部的严格模式,所以changeWeather中的this是undefined
                
                // 获取原来的isHot值
                const isHot = this.state.isHot
                // !!! 状态(state)不可直接更改,要借助一个内置的API去更改
                // 状态必须通过setState进行更新,且更新是一种合并,不是替换,即:未涉及更新的参数不会丢失
                this.setState({isHot: !isHot})

                // 不可取: this.state.isHot = !isHot
            }
        }


        // 2.渲染组件到页面
        ReactDOM.render(<Weather/>, document.getElementById('test'))


    </script>

</body>
</html>

state的简写方式

class Weather extends React.Component {
            
    // 初始化状态
    state = {isHot: true}

    render() {
        const {isHot} = this.state
        return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
    }

    // 自定义方法——赋值语句+箭头函数
    changeWeather = ()=>{
        const isHot = this.state.isHot
        this.setState({isHot: !isHot})
    }
}

理解

  1. state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
  2. 组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件)

2.props

props是只读的

class Test extends React.Component {
    render() {
        console.log(this)
        return <div></div>
    }
}

ReactDOM.render(<Test key1="value1" key2="value2" />, document.getElementById('test'))

image-20220301203341626

批量传递props

class Person extends React.Component {
            
    render() {
        console.log(this)
        const {name, age, sex} = this.props
        return (
            <ul>
                <li>姓名:{name}</li>
                <li>年龄:{age}</li>
                <li>性别:{sex}</li>
            </ul>
        )
    }

}

const p = {name: 'Tom', age: 18, sex: '男'}
ReactDOM.render(<Person {...p} />, document.getElementById('test1'))
ReactDOM.render(<Person name='Amy' age={} sex='女' />, document.getElementById('test2'))

image-20220301204242744

展开运算符

let arr1 = [1, 3, 5, 7, 9]
let arr2 = [2, 4, 6, 8, 10]
console.log(arr1)
console.log(...arr1) // 展开一个数组

let arr3 = [...arr1, ...arr2] // 连接两个数组
console.log(arr3)

function sum(...numbers) { // 数组传参
return numbers.reduce((preValue, currentValue) => {
  return preValue + currentValue
})
}
console.log(sum(1, 2, 3, 4, 5))

let person1 = { name: 'tom', age: 18 }
let person2 = { ...person1 } // 构造字面量对象
person1.name = 'jerry'
console.log('person2 :>> ', person2);

let person3 = { ...person1, name: 'jack', address: 'where' } // 复制对象并修改
console.log('person3 :>> ', person3);

image-20220301212807638

对props进行限制

原因:如果不对props加以限制,其他输入数据的人无法得知props需要的数据类型,例如ReactDOM.render(<Person name='Amy' age='19' sex='女' />, document.getElementById('test2'))ReactDOM.render(<Person name='Amy' age={19} sex='女' />, document.getElementById('test2'))

预期效果:

  • 姓名必须指定,且为字符串类型;
  • 性别为字符串类型,如果性别没有指定,默认为男
  • 年龄为字符串类型,且为数字类型,默认值为18

限制方法:

  1. 引入依赖包

    <script type="text/javascript" src="../../js/prop-types.js"></script>
    
  2. 限制标签属性:

    Person.propTypes = {
        name: PropTypes.string.isRequired, // 限制name必传,为字符串
        sex: PropTypes.string, // 限制sex为字符串
        age: PropTypes.number, // 限制age为数值
        speak: PropTypes.func // 限制speak为函数
    }
    Person.defaultProps = {
        sex: '男',
        age: 18
    }
    
  3. 效果:

    ReactDOM.render(<Person name={120}  />, document.getElementById('test2'))
    

    image-20220312104011074

props的简写方式

class Person extends React.Component {

    // 进行限制
    static propTypes = {
        name: PropTypes.string.isRequired, // 限制name必传,为字符串
        sex: PropTypes.string, // 限制sex为字符串
        age: PropTypes.number, // 限制age为数值
        speak: PropTypes.func // 限制speak为函数
    }
    static defaultProps = {
        sex: '男',
        age: 18
    }
    
    render() {
        /* console.log(this) */
        const {name, age, sex} = this.props
        return (
            <ul>
                <li>姓名:{name}</li>
                <li>年龄:{age+1}</li>
                <li>性别:{sex}</li>
            </ul>
        )
    }

}

构造器与props

constructor(props) {
    super(props)
    console.log('constructor',this.props);
}

构造器是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props,极其罕见

如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。

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

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

函数式组件使用props

function Person(props) {
    console.log(props);
    const {name, age, sex} = props
    return (
            <ul>
                <li>姓名:{name}</li>
                <li>年龄:{age}</li>
                <li>性别:{sex}</li>
            </ul>
        )
}

ReactDOM.render(<Person name='Jack' age={20} sex='男' />, document.getElementById('test3'))

image-20220312135358831

添加限制:

Person.propTypes = {
    name: PropTypes.string.isRequired, // 限制name必传,为字符串
    sex: PropTypes.string, // 限制sex为字符串
    age: PropTypes.number, // 限制age为数值
}
Person.defaultProps = {
    sex: '男',
    age: 18
}

ReactDOM.render(<Person name={100}/>, document.getElementById('test3'))

image-20220312135740515

理解

  • 每个组件对象都会有props(properties的简写)属性
  • 组件标签的所有属性都保存在props中

作用:

  1. 通过标签属性从组件外向组件内传递变化的数据
  2. 注意: 组件内部不要修改props数据

3.refs

refs收集多组ref

class Demo extends React.Component {

    showThis = () => {
        console.log(this)
    }

    render() {
        return (
            <div>
                <input ref="input1" type="text" placeholder="点击按钮提示数据"/>&nbsp;
                <button ref="button" onClick={this.showThis}>点击提示左侧输入框数据</button>&nbsp;
                <input ref="input2" type="text" placeholder="失去焦点提示数据" />
            </div>
        )
    }
}

image-20220312154333736

效果

GIF 2022-3-12 15-53-43

字符串形式的ref - 不太推荐

// 创建组件
class Demo extends React.Component {

    // 展示左侧输入框数据
    showLeft = () => {
        const {input1} = this.refs
        alert(input1.value)
    }

    // 展示右侧输入框数据
    showRight = () => {
        const {input2} = this.refs
        alert(input2.value)
    }

    render() {
        return (
            <div>
                <input ref="input1" type="text" placeholder="点击按钮提示数据"/>&nbsp;
                <button onClick={this.showLeft}>点击提示左侧输入框数据</button>&nbsp;
                <input ref="input2" onBlur={this.showRight} type="text" placeholder="失去焦点提示数据" />
            </div>
        )
    }
}

// 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('test'))

过时 API:String 类型的 Refs

如果你之前使用过 React,你可能了解过之前的 API 中的 string 类型的 ref 属性,例如 "textInput"。你可以通过 this.refs.textInput 来访问 DOM 节点。我们不建议使用它,因为 string 类型的 refs 存在 一些问题。它已过时并可能会在未来的版本被移除。

回调函数形式的ref

class Demo extends React.Component {

    // 展示左侧输入框数据
    showLeft = () => {
        const {input1} = this
        alert(input1.value)
    }

    // 展示右侧输入框数据
    showRight = () => {
        const {input2} = this
        alert(input2.value)
    }

    render() {
        return (
            <div>
                <input ref={(c) => {this.input1 = c}} type="text" placeholder="点击按钮提示数据"/>&nbsp;
                <button onClick={this.showLeft}>点击提示左侧输入框数据</button>&nbsp;
                <input ref={c => this.input2 = c} onBlur={this.showRight} type="text" placeholder="失去焦点提示数据" />
            </div>
        )
    }
}

回调ref中的调用次数:

  • 首次渲染:调用1次
  • 页面更新:调用2次

关于回调 refs 的说明

如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

class Demo extends React.Component {

    state = {isHot: true}

    showInfo = () => {
        const {input1} = this
        alert(input1.value)
    }

    changeWeather = () => {
        const {isHot} = this.state
        this.setState({isHot: !isHot})
    }

    render() {
        const {isHot} = this.state
        return (
            <div>
                <h2>今天天气很{isHot ? '炎热' : '凉爽'}</h2>
                <input ref={(c) => {this.input1 = c; console.log('@', c)}} type="text" placeholder="点击按钮提示数据"/>&nbsp;
                <button onClick={this.showInfo}>点击提示左侧输入框数据</button>&nbsp;
                <button onClick={this.changeWeather}>点击修改天气</button>
            </div>
        )
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U9lwpWEj-1672296614662)(null)]

避免多次回调:将 ref 的回调函数定义成 class 的绑定函数

saveInput = (c) => {
    this.input1 = c;
    console.log('@', c)
}

render() {
    const {isHot} = this.state
    return (
        <div>
            <h2>今天天气很{isHot ? '炎热' : '凉爽'}</h2>
            <input ref={this.saveInput} type="text" placeholder="点击按钮提示数据"/>&nbsp;
            <button onClick={this.showInfo}>点击提示左侧输入框数据</button>&nbsp;
            <button onClick={this.changeWeather}>点击修改天气</button>
        </div>
    )
}

createRef API

官方最推荐的创建ref的形式

class Demo extends React.Component {

    /*
        React.createRef调用后可以返回一个容器,该容器可以存储被ref标识的节点,该容器是“专人专用”的
    */
    myRefLeft = React.createRef()
    myRefRight = React.createRef()

    // 展示左侧输入框数据
    showLeft = () => {
        alert(this.myRefLeft.current.value)
    }

    // 展示右侧输入框数据
    showRight = () => {
        alert(this.myRefRight.current.value)
    }

    render() {
        return (
            <div>
                <input ref={this.myRefLeft} type="text" placeholder="点击按钮提示数据"/>&nbsp;
                <button onClick={this.showLeft}>点击提示左侧输入框数据</button>&nbsp;
                <input ref={this.myRefRight} onBlur={this.showRight} type="text" placeholder="失去焦点提示数据" />
            </div>
        )
    }
}

事件处理

  1. 通过onXxx属性指定事件处理函数(注意大小写)

    (1)React使用的是自定义(合成)事件, 而不是使用的原生DOM事件——为了更好的兼容性

    (2)React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)——高效

  2. 通过event.target得到发生事件的DOM元素对象——不要过度使用ref

收集表单数据

效果:

GIF 2022-3-12 20-15-45

非受控组件

页面内输入类DOM(input、checkbox、radio等)都是现用现取

class Login extends React.Component {
    handleSubmit = (event) => {
        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" /><br/>
                密码:<input ref={c => this.password = c} type="password" name="password" /><br/>
                <button>登录</button>
            </form>
        )
    }
}

受控组件

随着输入直接维护到状态中,需要时直接从状态中取得(类比:vue的双向数据绑定)

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}`)
    }

    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                用户名:<input onChange={this.saveUsername} type="text" name="username" /><br/>
                密码:<input onChange={this.savePassword} type="password" name="password" /><br/>
                <button>登录</button>
            </form>
        )
    }
}

GIF 2022-3-12 20-57-33

高阶函数与函数柯里化

// 保存表单数据到状态中
saveFormData = (dataType) => {
    return ((event) => {
        this.setState({[dataType]:event.target.value})
    })
}

render() {
    return (
        <form onSubmit={this.handleSubmit}>
            用户名:<input onChange={this.saveFormData('username')} type="text" name="username" /><br/>
            密码:<input onChange={this.saveFormData('password')} type="password" name="password" /><br/>
            <button>登录</button>
        </form>
    )
}

js方括号

let a = 'name'

let obj = {}
let obj1 = {}

obj[a] = 'tom'
obj1.a = 'tommy'

console.log(obj)
console.log(obj1)

image-20220315171516405

高阶函数

如果一个函数符合下面两个规范中的任何一个,那该函数就是高阶函数

  1. A函数接收的参数是一个函数
  2. A函数调用的返回值是一个函数

常见的高阶函数:PromisesetTimeoutarr.map()

函数的柯里化

通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式

/*function sum(a, b, c) {
    return a+b+c
}
result = sum(1, 2, 3)*/

function sum(a) {
    return (b) => {
        return (c) => {
            return a+b+c
        }
    }
    }
result = sum(1)(2)(3)
console.log(result);

不用柯里化的写法

// 不用柯里化
saveFormDataN = (dataType, event) => {
    this.setState({[dataType]:event.target.value})
}

render() {
    return (
        <form onSubmit={this.handleSubmit}>
            用户名:<input onChange={(event)=>{this.saveFormDataN('username', event)}} type="text" name="username" /><br/>
            密码:<input onChange={(event)=>{this.saveFormDataN('password', event)}} type="password" name="password" /><br/>
            <button>登录</button>
        </form>
    )
}

组件的生命周期

理解生命周期

生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子

// 创建组件
// 生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
class Demo extends React.Component {
    state = { opacity: 1 }

    death = () => {
        // 卸载组件
        ReactDOM.unmountComponentAtNode(document.getElementById('test'))
    }

    // 组件挂载完毕调用
    componentDidMount() {
        this.timer = setInterval(() => {
            // 获取原状态
            let { opacity } = this.state
            // -0.1
            opacity -= 0.1
            if (opacity <= 0) opacity = 1
            // 设置新状态
            this.setState({ opacity })
        }, 200)
    }

    // 组件将要卸载调用
    componentWillUnmount() {
        clearInterval(this.timer)
    }

    // 初始化渲染、状态更新调用
    render() {
        console.log('render');
        return (
            <div>
                <h2 style={{ opacity: this.state.opacity }}>React学不会了可咋办捏</h2>
                <button onClick={this.death}>不活了</button>
            </div>
        )
    }
}

// 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('test'))
  1. 组件从创建到死亡它会经历一些特定的阶段。
  2. React组件中包含一系列钩子函数(生命周期回调函数), 会在特定的时刻调用。
  3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。

旧版生命周期

流程图

react生命周期(旧)

setState()与forceUpdate()流程

// 创建组件
class Count extends React.Component {

    // 构造器
    constructor(props) {
        console.log('Count - constructor')
        super(props)
        // 初始化状态
        this.state = {count: 0}
    }

    // +1按钮的回调
    add = () => {
        // 获取原状态
        const {count} = this.state
        // 更新状态
        this.setState({count: count+1})
    }

    // 卸载组件按钮的回调
    death = () => {
        ReactDOM.unmountComponentAtNode(document.getElementById('test'))
    }

    // 强制更新按钮的回调
    force = () => {
        this.forceUpdate()
    }

    // 组件将要挂载的钩子
    componentWillMount() {
        console.log('Count - componentWillMount');
    }

    // 组件挂载完毕的钩子
    componentDidMount() {
        console.log('Count - componentDidMount');
    }

    // 组件将要卸载的钩子
    componentWillUnmount() {
        console.log('Count - componentWillUnmount');
    }

    // 控制组件更新的“阀门”
    shouldComponentUpdate() {
        console.log('Count - shouldComponentUpdate');
        return true // 重写shouldComponentUpdate()方法,必须有返回值
    }

    // 组件将要更新的钩子
    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>
        )
    }
}

// 渲染组件到页面
ReactDOM.render(<Count />, document.getElementById('test'))

setState

forceUpdate

父组件render流程

// 创建组件
class A extends React.Component {

    state = {carName: '奔驰'}

    change = () => {
        this.setState({carName: '特斯拉'})
    }

    render() {
        return (
            <div>
                <h2>我是A组件</h2>
                <button onClick={this.change}>换车</button>
                <B carName={this.state.carName}/>
            </div>
        )
    }
}

class B extends React.Component {

    // 组件将要接收新的props的钩子
    componentWillReceiveProps(props) { // 坑:第一次调用的不算
        console.log('B - componentWillReceiveProps', props);
    }

    // 控制组件更新的“阀门”
    shouldComponentUpdate() {
        console.log('B - shouldComponentUpdate');
        return true // 重写shouldComponentUpdate()方法,必须有返回值
    }

    // 组件将要更新的钩子
    componentWillUpdate() {
        console.log('B - componentWillUpdate');
    }

    // 组件更新完毕的钩子
    componentDidUpdate() {
        console.log('B - componentDidUpdate');
    }

    render() {
        console.log('B - render');
        return(
            <div>
                <h2>我是B组件,我的车是{this.props.carName}</h2>
            </div>
        )
    }
}

// 渲染组件到页面
ReactDOM.render(<A />, document.getElementById('test'))

父组件render

总结旧版生命周期的三个阶段

1.初始化阶段: 由ReactDOM.render()触发—初次渲染

  • constructor()
  • componentWillMount()
  • render()
  • componentDidMount() ====> 常用
    一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息

2.更新阶段: 由组件内部this.setState()或父组件重新render()触发

  • shouldComponentUpdate()
  • componentWillUpdate()
  • render() ====> 必须
  • componentDidUpdate()

3.卸载组件: 由ReactDOM.unmountComponentAtNode()触发

  • componentWillUnmount() ====> 常用
    一般在这个钩子中做收尾的事,例如:关闭定时器、取消订阅

新版生命周期

更新js文件,升级为17.0.1版本

<!-- 引入 React 核心库 -->
<script type="text/javascript" src="../js/17.0.1/react.development.js"></script>

<!-- 引入 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>

流程图

react生命周期(新)

getDerivedStateFromProps()

getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props。例如,实现 <Transition> 组件可能很方便,该组件会比较当前组件与下一组件,以决定针对哪些组件进行转场动画。

class Count extends React.Component {
    ...
    static getDerivedStateFromProps(props, state) {
        console.log('getDerivedStateFromProps', props, state);
        return props
    }
    ...
}
ReactDOM.render(<Count count={568}/>, document.getElementById('test'))

GIF 2022-3-19 18-47-30

getSnapshotBeforeUpdate()

getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()

此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。

应返回 snapshot 的值(或 null)。

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="newsList" ref="list">
                {this.state.newsArr.map((n, index) => {
                    return <div key={index} className="news">{n}</div>
                })}
            </div>
        )
    }
}

ReactDOM.render(<NewsList/>, document.getElementById('test'))

GIF 2022-3-20 20-36-43

总结新版生命周期的三个阶段

1.初始化阶段: 由ReactDOM.render()触发—初次渲染

  • constructor()
  • getDerivedStateFromProps
  • render()
  • componentDidMount() ====> 常用
    一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息

2.更新阶段: 由组件内部this.setSate()或父组件重新render触发

  • getDerivedStateFromProps
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate
  • componentDidUpdate()

3.卸载组件: 由ReactDOM.unmountComponentAtNode()触发

  • componentWillUnmount() ====> 常用
    一般在这个钩子中做收尾的事,例如:关闭定时器、取消订阅

重要的钩子

  • render:初始化渲染或更新渲染调用
  • componentDidMount:开启监听, 发送ajax请求
  • componentWillUnmount:做一些收尾工作, 如: 清理定时器

即将废弃的钩子

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。

DOM的Diffing算法

当对比两棵树时,React 首先比较两棵树的根节点。不同类型的根节点元素会有不同的形态。

Diffing最小的粒度是标签。

“粒度”表示的是精确程度问题。粗粒度角度描述一个系统,是关注系统中大的组件;细粒度角度描述一个系统是从组成大组件的小组件,或者更小组件的角度认识系统。

系统功能一般又分为多个模块,大的功能又会分为若干模块或者步骤,粒度一步一步细化,直到最终的某个用户操作(输入内容,下拉选择,上传文件,点击按钮等),具体的功能最终得到实现。这是一个粒度由粗到细的过程。反之就是由细到粗。
再从代码设计的角度来说。代码的结构是由类型之间的关联起来的系统。系统的整体结构(架构)就是系统最粗的粒度,代码也同系统功能一样,也是有模块划分(可能类似功能结构划分,也可能有所区别)。那么从复杂结构代码模块,到其细小的组成部分就是粒度由粗到细的过程。

验证Diffing算法

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'))

GIF 2022-3-20 21-00-40

两个输入框中的输入内容均没有丢失,证明Diffing算法是真实存在的

虚拟DOM中key的作用

简单地说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用

详细的说:当状态中的数据发生变化时,react会根据新数据生成新的虚拟DOM,随后react进行新虚拟DOM与旧虚拟DOM的diff比较,规则如下:

  1. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
    • 若虚拟DOM中内容未变,直接使用之前的真实DOM
    • 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
  2. 旧虚拟DOM中未找到与新虚拟DOM相同的key:
    • 根据数据创建新的真实DOM,随后渲染到页面

用index作为key可能会引发的问题

  1. 若对数据进行:逆序添加、逆序删除等破坏顺序的操作:
    • 会产生没有必要的真实DOM更新 ===> 界面效果没问题,但效率低
  2. 如果结构中包含输入类的DOM:
    • 会产生错误DOM更新 ===> 界面有问题

注意:如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key没有问题

class Person extends React.Component {
    state = {
        persons:[
        {id:1, name:'小张', age:18},
        {id:2, name:'小王', age:20}
        ]
    }

    add = () => {
        const {persons} = this.state
        const p = {id:persons.length+1, name:'小李', age:35}
        this.setState({persons: [p, ...persons]})
    }

    render() {
        return(
            <div>
                <h2>展示人员信息</h2>
                <h3>使用index(索引值)作为key</h3>
                <button onClick={this.add}>添加一个小李</button>
                <ul>
                    {
                        this.state.persons.map((personObj, index) => {
                            return <li key={index}>{personObj.name}----{personObj.age}<input type="text"/></li>
                        })
                    }
                </ul>
                <hr/>
                <h3>使用obj.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'))

GIF 2022-3-20 22-40-32

开发中如何选择key

  1. 最好使用每条数据的唯一标识作为key,比如id、手机号、学号、身份证号等
  2. 如果确定只是简单的展示数据,使用index亦可
on extends React.Component {
    state = {
        persons:[
        {id:1, name:'小张', age:18},
        {id:2, name:'小王', age:20}
        ]
    }

    add = () => {
        const {persons} = this.state
        const p = {id:persons.length+1, name:'小李', age:35}
        this.setState({persons: [p, ...persons]})
    }

    render() {
        return(
            <div>
                <h2>展示人员信息</h2>
                <h3>使用index(索引值)作为key</h3>
                <button onClick={this.add}>添加一个小李</button>
                <ul>
                    {
                        this.state.persons.map((personObj, index) => {
                            return <li key={index}>{personObj.name}----{personObj.age}<input type="text"/></li>
                        })
                    }
                </ul>
                <hr/>
                <h3>使用obj.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'))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/126370.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何高效阅读一篇论文

如何阅读一篇论文&#xff08;做好阅读笔记&#xff09;阅读步骤第一遍第二遍第三遍上哪里找论文paperswithcodeconnectedpaperslabml.ai 深度学习论文实现labml.ai 热门研究论文阅读步骤 第一遍 第一次通过的目的是大致了解论文。 阅读作者姓名、标题、摘要、简介、小节标题…

create first django

django-admin startproject first 1. 运行第一个django.py文件 python manage.py runserver 2. 建立第一个app python manage.py startapp first_app 修改settings.py&#xff0c;将first_app加入到下面中 然后修改views.py 然后修改urls.py配置导入view文件 前面是一个正则表达…

一文速学-Pandas处理时间序列数据-时间/日期操作详解

前言 关于Pandas处理时间序列数据我已经有写过两篇处理文章了&#xff1a; 一文速学-Pandas中DataFrame转换为时间格式数据与处理 一文速学-Pandas处理时间序列数据操作详解 日常处理一些数据和业务上需求&#xff0c;其实还是十分常用到时序数据的&#xff0c;一些处理方…

堆排序,建初始堆以及优先队列(priority_queue)

1.堆&#xff1a; 如果有一个关键码的集合K {k0&#xff0c;k1&#xff0c; k2&#xff0c;…&#xff0c;kn-1}&#xff0c;把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中&#xff0c;并满足&#xff1a;Ki < K2i1 且 Ki<K2i2 &#xff0c;则称为小堆…

Docker部署jenkins配置公私钥拉取代码

容器内配置公私钥 先进入部署Jenkinns中的容器&#xff0c;在docker容器内生成公私钥 ssh-keygen -t rsajenkins 配置私钥信息 在Dashbord->凭据->系统->全局凭据中新增一个凭据 将公钥配置在gitlab 正常这么配制就可以了&#xff0c;但是在jenkins上发现使用ssh…

如何快速掌握代币经济学

如何研究加密世界里的Token? 先看一组数据&#xff1a;截至2022年&#xff0c;市面上大约有6000种加密货币(或者更多&#xff09;。这对投资者来说当然是一个很大的机会。然而&#xff0c;在2021年&#xff0c;投资者在Crypto项目遇到欺诈&#xff0c;损失的金额120亿美元。因…

2022年河北沃克金属制品有限公司助力河北石家庄电子商务资源对接暨电商直播选品大会圆满落幕!

会议主题&#xff1a;聚合电商直播优势资源 赋能产业发展消费增长 主题活动&#xff1a;2022河北•石家庄电子商务资源对接暨电商直播选品大会 承办日期&#xff1a;2022年12月26日至2022年12月27日 主办单位&#xff1a;石家庄市商务局 指导单位&#xff1a;河北省商务厅 …

基于K8s的DevOps平台实践(一)

文章目录前言1. DevOps介绍&#x1f351; 瀑布式流程&#x1f351; 敏捷开发&#x1f351; DevOps2. Jenkins初体验&#x1f351; K8s环境中部署jenkins&#x1f351; 安装汉化插件3. Jenkins基本使用演示&#x1f351; 演示目标&#x1f351; 演示准备&#x1f351; 演示过程4…

04贪心算法

文章目录背包问题活动安排问题最优装载问题删数问题最优服务次序贪心算法 在对问题求解时&#xff0c;总是做出在当前看来是最好的选择。也就是说&#xff0c;不从整体最优上加以考虑&#xff0c;他所做出的是在某种意义上的局部最优解。 过程&#xff1a; 建立数学模型来描述问…

奖励视频 — Verasity 的最新专利意味着什么?

目录 Verasity 的最新专利涵盖哪些内容&#xff1f; 美国专利审批流程——拒绝和批准 主张我们的专利并寻求许可费 这对 Verasity 意味着什么&#xff1f; 近日&#xff0c;我们宣布Verasity 已在全球最大的广告和媒体市场美国获得奖励视频专利。 该专利及其全部内容可在此…

Vue--》超详细教程——vite脚手架的搭建与使用

目录 vite 创建 vite 项目 目录文件的构成 vite项目的运行流程 开发者工具安装 vite vue官方提供了两种快速创建工程化的SPA项目的方式&#xff0c;一种是基于 vue-cli 创建的SPA项目&#xff0c;另一种就是基于 vite 创建的SPA项目。两者的区别如下&#xff1a; 说明v…

如何在电脑录屏?win10录屏快捷键ctrl+alt+

日常使用的电脑有很多功能未被大家发现&#xff0c;比如可以录制屏幕视频&#xff1b;那如何在电脑录屏&#xff1f;win10电脑录屏有没有什么快捷键可以快速录制&#xff1f;下面就一起和小编来看看win10录屏快捷键是如何在电脑录屏的&#xff0c;有需要的朋友可以去试试看。 一…

观察者模式Observer

1.意图&#xff1a;定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。 2.结构 Subject&#xff08;目标&#xff09;知道它的观察者&#xff0c;可以有任意多个观察者观察同一个目标&#xff1…

Flutter GetX系列教程---GetxController

安装 将 GetX 添加到你的 pubspec.yaml 文件中 dependencies:get: ^4.6.5在需要用到的文件中导入&#xff0c;它将被使用。 import package:get/get.dart;GetxController介绍 在实际的项目开发过程中&#xff0c;我们不可能把UI代码、业务逻辑都放在一起处理&#xff0c;这…

Google 二次签名 导致 Facebook , Google 登录失败

前言&#xff1a; 最近接了几款游戏 里面携带了facebook 授权登录和google 授权问题 问题场景 在没有上线之前 我们运营和测试 验收都没有问题 但是把游戏包发到商店后再重商店下载出来就不能授权登录。 原因 因为App上传到Google Play后&#xff0c;Google Play 有个签名…

使用idea无法推送到gitee,显示head detached(游离分支)

在idea下将代码回退到某一历史版本&#xff0c;修改后push提醒detaced head&#xff0c;即处于游离状态&#xff0c;使用 git branch命令&#xff08;辅助git status查看提交状态&#xff09;查看&#xff1a; 解决方法 在git bash下***&#xff08;注意&#xff09;切换到项…

django使用一——规范化创建

背景 为便于后续维护&#xff0c;前期对django创建等操作做一些规范化要求 实操 1、【项目创建】先创建一个项目&#xff0c;其中SSS可以项目架构相关&#xff0c;XXX则是项目名称 常用命令&#xff1a; django startproject SSS_app_XXX如果提示报错django命令不存在&…

超全地牢场景unity3d模型素材网站整理

家人们&#xff0c;最近我找到了一个很好用的地牢场景unity3d模型免费素材网站 资源贼多&#xff0c;重点是免费&#xff01;&#xff01;&#xff01;嘿嘿嘿&#xff01;&#xff01;&#xff01;感兴趣的可以进去看看 接下来就给大家介绍一下我珍藏已久的网站&#xff1a; …

string类的基本使用及模拟实现(深浅拷贝)

目录 浅拷贝与深拷贝 string的基本使用和模拟实现 浅拷贝与深拷贝 namespace lyl { class string { public: string(const char* str) :_str(new char[strlen(str)1]) { strcpy(_str, str); } ~string() …

TPM零知识学习九 —— TPM规范学习之TPM介绍

前边的系列文章主要从实际平台移植和TPM环境搭建的角度讲解了TPM&#xff0c;现在到了踏实下来学习理论知识的时候了。从本篇文章开始&#xff0c;开启TPM2.0规范学习之旅。参考书目为&#xff1a;《A Practical Guide to TPM 2.0 — Using the Trusted Plaform Module in the …