React学习笔记02_面向组件编程

第2章:React面向组件编程

2.1. 基本理解和使用

2.1.1. 使用React开发者工具调试

在这里插入图片描述

2.1.2. 效果

函数式组件:
在这里插入图片描述
在这里插入图片描述

2.1.3. 注意

  1. 组件名必须首字母大写
  2. 虚拟DOM元素只能有一个根元素
  3. 虚拟DOM元素必须有结束标签
function Demo() {
    return <h2>自定义组件</h2>
}
const VDOM = (
    <div>
        <h2 className="title" id="{myId}">
            <span style={{color:'white'}}>{myData}</span>
        </h2>
        <Demo></Demo>
    </div>
)            
ReactDOM.render(VDOM, document.getElementById('test'))

2.1.4. 渲染类组件标签的基本流程

  1. React内部会创建组件实例对象
  2. 调用render()得到虚拟DOM, 并解析为真实DOM
  3. 插入到指定的页面元素内部
// 1.创建类式组件
class MyConponent extends React.Component {
    render() {
        // render是放在哪里?MyConponent类的原型对象上,供实例使用
        // render中的this是谁?MyConponent的实例对象<=>MyConponent组件实例对象
        return <h2>复杂组件</h2>
    }
}
// 2.渲染组件到页面
ReactDOM.render(<MyConponent/>, document.getElementById('test'))
/* 执行了ReactDOM.render(<MyConponent/>.....)之后,发生了什么?
    1.React解析组件标签,找到了MyConponent组件
    2.发现组件是使用类定义的,随后new出该类的实例,并通过该实例调用到原型上的render方法。
    3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面。
*/

2.2. 组件三大核心属性1: state

2.2.1. 效果

需求: 定义一个展示天气信息的组件

  1. 默认展示天气炎热 或 凉爽
  2. 点击文字切换天气
// 1.创建类式组件
class Weather extends React.Component {
    constructor(props) {
        super(props)
        // 初始化状态
        this.state = {
            isHot: false,
            wind: '小风'
        }
        // bind()帮助生成新的函数,解决changeWeather中this指向问题
        this.changeWeather = this.changeWeather.bind(this)
    }
    render() {
        console.log(this)
        const {isHot, wind} = this.state
        return <h2 onClick={this.changeWeather}>今天天气很{isHot?'炎热':'寒冷'},有点{wind}</h2>
    }
    changeWeather() {
        // 由于changeWeaather是作为onClick的回调,所以不是通过实例调用的,是直接调用
        // 类中的方法默认直接开起了局部的严格模式,所以changeWeather中的this为undefined
        console.log(this)
        // state状态不可以直接更改

        const isHot = this.state.isHot
        // 状态必须通过setState进行更新,且更新是一种合并,不是替换
        this.setState({isHot: !isHot})
        // this.state.isHot = !isHot // 错误写法

    }
}
// 2.渲染组件到页面
ReactDOM.render(<Weather/>, document.getElementById('test'))
// 1.创建类式组件
class Weather2 extends React.Component {
    constructor(props) {
        super(props)
    }
    state = {
        isHot: false,
        wind: '小风'
    }
    render() {
        const {isHot, wind} = this.state
        return <h2 onClick={this.changeWeather}>今天天气很{isHot?'炎热':'寒冷'},有点{wind}</h2>
    }
    // 箭头函数没有自己的this,会往外找
    // 自定义方法——用赋值语句的形式+箭头函数
    changeWeather = ()=> {
        const isHot = this.state.isHot
        this.setState({isHot: !isHot})
    }
}
// 2.渲染组件到页面
ReactDOM.render(<Weather2/>, document.getElementById('test'))

2.2.2. 理解

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

2.2.3. 强烈注意

  1. 组件中render方法中的this为组件实例对象
  2. 组件自定义的方法中this为undefined,如何解决?
    • 强制绑定this: 通过函数对象的bind()
    • 箭头函数
  3. 状态数据,不能直接修改或更新
  4. 严格模式中的this不能指向window

2.3. 组件三大核心属性2: props

2.3.1. 效果(类式组件和函数式组件)

需求: 自定义用来显示一个人员信息的组件

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

类式组件:

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() {
        const {name, sex, age} = this.props
        // props是只读的
        // this.props.name = 'jack' // 此行代码会报错
        return (
            <ul>
                <li>姓名:{name}</li>
                <li>性别:{sex}</li>
                <li>年龄:{age+1}</li>
            </ul>
        )
    }
}

ReactDOM.render(<Person name="Tom"  age={18} speak={speak}/>, document.getElementById('test')) 

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

function speak() {
    console.log('我会说话')
}

函数式组件:

function Person(props){
    const {name, sex, age} = props
    return (
        <ul>
            <li>姓名:{name}</li>
            <li>性别:{sex}</li>
            <li>年龄:{age+1}</li>
        </ul>
    )
}
// 对标签属性进行类型、必要性的限制
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="Tom"  age={18} speak={speak}/>, document.getElementById('test')) 

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

function speak() {
    console.log('我会说话')
}

2.3.2. 理解

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

2.3.3. 作用

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

2.3.4. 编码操作

  1. 内部读取某个属性值

    this.props.name
    
  2. 对props中的属性值进行类型限制和必要性限制

    • 第一种方式(React v15.5 开始已弃用):
    Person.propTypes = {
        name: React.PropTypes.string.isRequired,
        age: React.PropTypes.number
    }
    
    • 第二种方式(新):使用prop-types库进限制(需要引入prop-types库)
    Person.propTypes = {
        name: PropTypes.string.isRequired,
        age: PropTypes.number. 
    }
    
  3. 扩展属性: 将对象的所有属性通过props传递

    <Person {...person}/>
    
  4. 默认属性值:

    Person.defaultProps = {
        age: 18,
        sex:'男'
    }
    
  5. 组件类的构造函数

    constructor(props){
        // 构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
        super(props)
        console.log(props)//打印所有属性
    }
    

2.4. 组件三大核心属性3: refs与事件处理

2.4.1. 效果

需求: 自定义组件, 功能说明如下:

  1. 点击按钮, 提示第一个输入框中的值
  2. 当第2个输入框失去焦点时, 提示这个输入框中的值
    效果如下:

String类型的ref,过时了,以后可能会被废弃

class Demo2 extends React.Component{
    // 展示左侧栏的数据
    showData = ()=> {
        const {input1} = this.refs
        alert(input1.value)
    }
    // 展示右侧栏的数据
    showData2 = ()=> {
        const {input2} = this.refs
        alert(input2.value)
    }
    render() {
        return (
            <div>
                <input ref="input1" type="text"/>
                <button onClick={this.showData}>点我提示左侧的数据</button>
                <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
            </div>
        )
    }
}
ReactDOM.render(<Demo2/>, document.getElementById('test'));

回调函数形式的ref:

class Demo3 extends React.Component{
    // 展示左侧栏的数据
    showData = ()=> {
        const {input1} = this
        alert(input1.value)
    }
    // 展示右侧栏的数据
    showData2 = ()=> {
        const {input2} = this
        alert(input2.value)
    }
    render() {
        return (
            <div>
                <input ref={(currentNode)=>{this.input1 = currentNode}} type="text"/>
                <button onClick={this.showData}>点我提示左侧的数据</button>
                <input ref={c => this.input2 = c} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
            </div>
        )
    }
}
ReactDOM.render(<Demo3/>, document.getElementById('test'));

createRef创建ref容器:最麻烦的一种,但是是React最推荐的

class Demo4 extends React.Component{
    /*
        React.createRef()调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是专人专用的
    */
    myRef = React.createRef()
    myRef2 = React.createRef()
    showData = ()=> {
        alert(this.myRef.current.value)
    }
    showData2 = ()=> {
        alert(this.myRef2.current.value)
    }
    render() {
        return (
            <div>
                <input ref={this.myRef} type="text"/>
                <button onClick={this.showData}>点我提示左侧的数据</button>
                <input ref={this.myRef2} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
            </div>
        )
    }
}
ReactDOM.render(<Demo4/>, document.getElementById('test'));

2.4.2. 理解

组件内的标签可以定义ref属性来标识自己

2.4.3. 编码

  1. 字符串形式的ref

    <input ref="input1"/>
    
  2. 回调形式的ref

    <input ref={(c)=>{this.input1 = c}}
    
  3. createRef创建ref容器

    myRef = React.createRef() 
    <input ref={this.myRef}/>
    

注意:不要过度使用ref

class Demo4 extends React.Component{
    myRef = React.createRef()
    showData = ()=> {
        alert(this.myRef.current.value)
    }
    showData2 = (event)=> {
        alert(event.target.value)
    }
    render() {
        return (
            <div>
                <input ref={this.myRef} type="text"/>
                <button onClick={this.showData}>点我提示左侧的数据</button>
                <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
            </div>
        )
    }
}
ReactDOM.render(<Demo4/>, document.getElementById('test'));

2.4.4. 事件处理

  1. 通过onXxx属性指定事件处理函数(注意大小写)
    • React使用的是自定义(合成)事件, 而不是使用的原生DOM事件————为了更好的兼容性
    • React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)————为了高效
  2. 通过event.target得到发生事件的DOM元素对象

2.5. 收集表单数据

2.5.1. 效果

需求: 定义一个包含表单的组件
输入用户名密码后, 点击登录提示输入信息

非受控组件:

class Login extends React.Component {
    handelSubmit = (event)=> {
        event.preventDefault();
        
        const {username,password} = this
        alert(`你输入的账号是:${username.value},密码是:${password.value}`)
    }
    render() {
        return (
            <form action="" onSubmit={this.handelSubmit}>
                用户名:<input ref={c=>this.username=c} type="text" name="username"/>
                密码:<input ref={c=>this.password=c} type="password" name="password"/>
                <button>登陆</button>
            </form>
        )
    }
}
ReactDOM.render(<Login/>, document.getElementById('test'));

受控组件:

class Login2 extends React.Component {

    state = {
        username: '',
        password: ''
    }

    handelSubmit = (event)=> {
        event.preventDefault();
        
        const {username,password} = this.state
        alert(`你输入的账号是:${username},密码是:${password}`)
    }

    saveUsername = (event)=> {
        this.setState({username: event.target.value})
    }

    savePassword = (event)=> {
        this.setState({password: event.target.value})
    }

    render() {
        return (
            <form action="" onSubmit={this.handelSubmit}>
                用户名:<input onChange={this.saveUsername} type="text" name="username"/>
                密码:<input onChange={this.savePassword} type="password" name="password"/>
                <button>登陆</button>
            </form>
        )
    }
}
ReactDOM.render(<Login2/>, document.getElementById('test'));

优化:使用柯里化

class Login3 extends React.Component {

    state = {
        username: '',
        password: ''
    }

    handelSubmit = (event)=> {
        event.preventDefault();
        
        const {username,password} = this.state
        alert(`你输入的账号是:${username},密码是:${password}`)
    }

    /*
        高阶函数:只要符合以下任意一个,就是高阶函数
            1.若A函数,接收的参数的是一个函数,那么A函数就可以称之为高阶函数。
            2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
            常见的高阶函数有:Promise、setTimeout、arr.map()等等
            
        函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
    */
    saveFormData = (dataType)=> {
        return (event) => {
            console.log(event.target.value)
            // obj[key] = value
            this.setState({[dataType]: event.target.value})
        }
    }

    render() {
        return (
            <form action="" onSubmit={this.handelSubmit}>
                用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>
                密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>
                <button>登陆</button>
            </form>
        )
    }
}
ReactDOM.render(<Login3/>, document.getElementById('test'));

函数的柯里化:

function sum(a) {
    return (b)=>{
        return (c)=>{
            return a+b+c
        }
    }
}

const result = sum(1)(2)(3)
console.log(result) // 6

不用柯里化:

class Login4 extends React.Component {

    state = {
        username: '',
        password: ''
    }

    handelSubmit = (event)=> {
        event.preventDefault();
        
        const {username,password} = this.state
        alert(`你输入的账号是:${username},密码是:${password}`)
    }

    saveFormData = (dataType,event)=> {
        this.setState({[dataType]: event.target.value})
    }

    render() {
        return (
            <form action="" onSubmit={this.handelSubmit}>
                用户名:<input onChange={event=>this.saveFormData('username',event)} type="text" name="username"/>
                密码:<input onChange={event=>this.saveFormData('password',event)} type="password" name="password"/>
                <button>登陆</button>
            </form>
        )
    }
}
ReactDOM.render(<Login4/>, document.getElementById('test'));

2.5.2. 理解

包含表单的组件分类

  1. 受控组件
  2. 非受控组件

2.6. 组件的生命周期

2.6.1. 效果

需求:定义组件实现以下功能:

  1. 让指定的文本做显示 / 隐藏的渐变动画
  2. 从完全可见,到彻底消失,耗时2S
  3. 点击“不活了”按钮从界面中卸载组件
class Life extends React.Component {
    state = {opacity:0.5}
    death = ()=> {
        // 卸载组件
        ReactDOM.unmountComponentAtNode(document.getElementById('test'))
    }

    // 组件挂载完毕
    componentDidMount() {
        this.timer = setInterval(() => {
            let {opacity} = this.state
            opacity -= 0.1
            if(opacity <= 0) opacity = 1
            this.setState({opacity})
        }, 200);
    }

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

    // render调用的时机:初始化渲染、状态更新之后
    render() {
        return (
            <div>
                <h2 style={{opacity:this.state.opacity}}>学不会怎么办</h2>
                <button onClick={this.death}>不活了</button>
            </div>
        )
    }
}
ReactDOM.render(<Life/>, document.getElementById('test'));

2.6.2. 理解

  1. 组件从创建到死亡它会经历一些特定的阶段。
  2. React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
  3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。
  4. 生命周期回调函数<=>生命周期钩子函数<=>生命周期函数<=>生命周期钩子

2.6.3. 生命周期流程图(旧)

在这里插入图片描述

生命周期的三个阶段(旧)

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

    1. constructor()
    2. componentWillMount()
    3. render()
    4. componentDidMount() ===> 常用 一般在这个钩子做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
  2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发

    1. shouldComponentUpdate()
    2. componentWillUpdate()
    3. render()
    4. componentDidUpdate()
  3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发

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

2.6.4. 生命周期流程图(新)

在这里插入图片描述

生命周期的三个阶段(新)

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

    1. constructor()
    2. getDerivedStateFromProps ==> 从props得到一个派生的状态,使用场景比较少。若state的值在任何时候都取决于props,那么可以使用

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

          static getDerivedStateFromProps(props) {
              return props
          }
      
    3. render()
    4. componentDidMount()
  2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发

    1. getDerivedStateFromProps
    2. shouldComponentUpdate()
    3. render()
    4. getSnapshotBeforeUpdate ==> 在更新前获得快照,任何值都可以作为快照值

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

      class NewList 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="list" ref="list">
                  {
                      this.state.newsArr.map((n,index)=> {
                          return <div className="news" key={index}>{n}</div>
                      })
                  }
                  </div>
              )
          }
      }
      ReactDOM.render(<NewList/>, document.getElementById('test'));
      
    5. componentDidUpdate()
  3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发

    1. componentWillUnmount()

2.6.5. 重要的勾子

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

2.6.6. 即将废弃的勾子

  1. componentWillMount
  2. componentWillReceiveProps
  3. componentWillUpdate

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

2.7. 虚拟DOM与DOM Diffing算法

2.7.1. 基本原理图

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值