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

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

简单组件:无状态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'))后发生了什么?
React解析组件标签,找到了Demo组件
发现组件是使用函数定义的,随后调用该函数,将返回的虚拟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()
继承
// 创建一个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()
- 类中的构造器不是必须写的,要对实例进行一些初始化的操作(如添加指定属性)时才写;
- 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的;
- 类中所定义的方法,都是放在了类的原型对象上,供实例去使用
类中可以直接写赋值语句。
类中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()
效果

复杂组件:有状态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'))后发生了什么?
- React解析组件标签,找到了Demo组件
- 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法
- 将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)
    }
}

解决方法:在构造器中绑定
this.changeWeather = this.changeWeather.bind(this)


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

代码
<!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})
    }
}
理解
- state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
- 组件被称为“状态机”,通过更新组件的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'))

批量传递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'))

展开运算符
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);
对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
限制方法:
-  引入依赖包 <script type="text/javascript" src="../../js/prop-types.js"></script>
-  限制标签属性: Person.propTypes = { name: PropTypes.string.isRequired, // 限制name必传,为字符串 sex: PropTypes.string, // 限制sex为字符串 age: PropTypes.number, // 限制age为数值 speak: PropTypes.func // 限制speak为函数 } Person.defaultProps = { sex: '男', age: 18 }
-  效果: ReactDOM.render(<Person name={120} />, document.getElementById('test2')) 
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'))

添加限制:
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'))

理解
- 每个组件对象都会有props(properties的简写)属性
- 组件标签的所有属性都保存在props中
作用:
- 通过标签属性从组件外向组件内传递变化的数据
- 注意: 组件内部不要修改props数据
3.refs
refs收集多组ref
class Demo extends React.Component {
    showThis = () => {
        console.log(this)
    }
    render() {
        return (
            <div>
                <input ref="input1" type="text" placeholder="点击按钮提示数据"/> 
                <button ref="button" onClick={this.showThis}>点击提示左侧输入框数据</button> 
                <input ref="input2" type="text" placeholder="失去焦点提示数据" />
            </div>
        )
    }
}

效果

字符串形式的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="点击按钮提示数据"/> 
                <button onClick={this.showLeft}>点击提示左侧输入框数据</button> 
                <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="点击按钮提示数据"/> 
                <button onClick={this.showLeft}>点击提示左侧输入框数据</button> 
                <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="点击按钮提示数据"/> 
                <button onClick={this.showInfo}>点击提示左侧输入框数据</button> 
                <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="点击按钮提示数据"/> 
            <button onClick={this.showInfo}>点击提示左侧输入框数据</button> 
            <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="点击按钮提示数据"/> 
                <button onClick={this.showLeft}>点击提示左侧输入框数据</button> 
                <input ref={this.myRefRight} onBlur={this.showRight} type="text" placeholder="失去焦点提示数据" />
            </div>
        )
    }
}
事件处理
-  通过onXxx属性指定事件处理函数(注意大小写) (1)React使用的是自定义(合成)事件, 而不是使用的原生DOM事件——为了更好的兼容性 (2)React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)——高效 
-  通过event.target得到发生事件的DOM元素对象——不要过度使用ref 
收集表单数据
效果:

非受控组件
页面内输入类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>
        )
    }
}

高阶函数与函数柯里化
// 保存表单数据到状态中
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)
高阶函数
如果一个函数符合下面两个规范中的任何一个,那该函数就是高阶函数
- A函数接收的参数是一个函数
- A函数调用的返回值是一个函数
常见的高阶函数:Promise、setTimeout、arr.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'))
- 组件从创建到死亡它会经历一些特定的阶段。
- 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'))


父组件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'))

总结旧版生命周期的三个阶段
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>
流程图

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

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

总结新版生命周期的三个阶段
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'))

两个输入框中的输入内容均没有丢失,证明Diffing算法是真实存在的
虚拟DOM中key的作用
简单地说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用
详细的说:当状态中的数据发生变化时,react会根据新数据生成新的虚拟DOM,随后react进行新虚拟DOM与旧虚拟DOM的diff比较,规则如下:
- 旧虚拟DOM中找到了与新虚拟DOM相同的key: 
  - 若虚拟DOM中内容未变,直接使用之前的真实DOM
- 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
 
- 旧虚拟DOM中未找到与新虚拟DOM相同的key: 
  - 根据数据创建新的真实DOM,随后渲染到页面
 
用index作为key可能会引发的问题
- 若对数据进行:逆序添加、逆序删除等破坏顺序的操作: 
  - 会产生没有必要的真实DOM更新 ===> 界面效果没问题,但效率低
 
- 如果结构中包含输入类的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'))

开发中如何选择key
- 最好使用每条数据的唯一标识作为key,比如id、手机号、学号、身份证号等
- 如果确定只是简单的展示数据,使用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'))

























