面向组件编程

面向组件编程

  • 函数式组件 —— 适用于简单组件
  • 类式组件 —— 适用于复杂组件

函数式组件

流程:

  1. 创建函数式组件
// 主要注意函数要大写,要有返回值
function MyComponent() {
    return <h2>我是用函数定义的组件</h2>
 }
  1. 将渲染组件到页面
// 注意函数式组件需要用标签表示
ReactDOM.render(<MyComponent/>, document.getElementById('test'));
  1. React解析组件表情啊,找到组件
  2. 发现组件是用函数定义的,随后调用函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中

类式组件

  1. 创建类式组件
// 需要继承React.Component
// 必须调用render函数
class MyComponent extends React.Component{
    render() {
        return <h2>我是用类定义的组件</h2>
    }
}
  1. 将渲染组件到页面
// 注意函数式组件需要用标签表示
ReactDOM.render(<MyComponent/>, document.getElementById('test'));
  1. React解析组件表情啊,找到组件
  2. 发现组件是用类定义的,随后用new出来该类的实例,并通过该实例调用到原型上的render方法
  3. 将该render返回的虚拟DOM转为真实DOM,随后呈现在页面上

组件实例的三个核心属性之一:State

对State理解:

人-状态-影响-行为

组件-状态-驱动-页面

可能出现的类中this问题
class Weather extends React.Component {

    constructor(props) {
        super(props);
        this.state = {isHot: false};
    }

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

ReactDOM.render(<Weather/>, document.getElementById('test'));
  • changeWeather放在哪里 —— Weather的原型对象上,供实例使用

  • 由于changeWeather是作为onClick的回调,所以不是实例调用的,是直接调用

  • 类中的方法默认开启局部的严格模式,所以changeWeather中的this为undefined

解决方法:

constructor(props) {
    super(props);
    this.state = {isHot: false};
    this.changeWeather = this.changeWeather.bind(this)
}
  • 通过bind方法修改this的指向对象并且创建新方法
  • 右边的this.changeWeather指的是类中的changeWeather方法,而左边的this.changeWeather是指向新方法的方法名
setState方法的使用
class Weather extends React.Component {

    constructor(props) {
        super(props);
        this.state = {isHot: false};
        this.changeWeather = this.changeWeather.bind(this)
    }

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

    changeWeather() {
        const isHot = this.state.isHot;
        this.setState({isHot:!isHot});
    }
}
精简化
class Weather extends React.Component {

    // 初始化方法
    state = {isHot: false, wind: '微风'};

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

    // 自定义方法 -- 用赋值语句的形式+箭头函数
    changeWeather = () => {
        const isHot = this.state.isHot;
        this.setState({isHot: !isHot});
    }
}
总结
  • 组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件)
  • 组件的render方法中的this为组件实例对象
  • 组件自定义的方法中this为undefined
    • 强制绑定this:通过函数对象的bind()
    • 箭头函数
  • 状态数据,不能直接修改或更新,需要使用setState()进行修改和更新

组件实例的三个核心属性之一: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>
        )
    }
}

ReactDOM.render(<Person name="Tom" age="123" sex="male"/>, document.getElementById('test'))
Prop限制

需要引入JS文件/prop-types.js

对Prop属性进行类型限制

Person.propTypes = {
    name: PropTypes.string.isRequired,
    sex: PropTypes.string,
    age: PropTypes.number,
    speak: PropTypes.func
};

对Prop属性进行默认设置

Person.defaultProps = {
    sex: '男',
    age: 18
};
精简化
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>
        )
    }

    static propTypes = {
        name: PropTypes.string.isRequired,
        sex: PropTypes.string,
        age: PropTypes.number,
        speak: PropTypes.func
    };

    static defaultProps = {
        sex: '男',
        age: 18
    };
}
小问题

构造器是否接受props,是否传递给super,取决于:是否希望在构造器中通过this访问props

但是这个问题几乎不会遇到,因为很少使用this.props

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

组件实例的三个核心属性之一:Ref与事件处理

字符串形式Ref

字符串形式Ref已经弃用,所以不推荐使用,使用过多会造成效率上的问题

class Demo 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" placeholder="点击按钮提示数据"/>
                &nbsp;
                <button ref="button" onClick={this.showData}>点我提示左侧的数据</button>
                &nbsp;
                <input onBlur={this.showData2} ref="input2" type="text" placeholder="失去焦点提示数据"/>
            </div>
        )
    }
}
回调函数形式Ref

关于回调refs说明:如果是内联函数的方法定义,在更新过程中会被执行两次,第一次传入参数null,第二次会传入参数DOM元素,这是因为在每次渲染时会创建一个新的函数实例,所以React会清空旧的ref并且设置新的。但是大多数情况下时无关紧要的。

回调函数形式就是通过回调函数将真实DOM返回到this的属性中

class Demo extends React.Component {

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

    showData2 = () => {
        const {input2} = this;
        alert(input2.value);
    }; 

    render() {
        return (
            <div>
                <input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示数据"/>
                &nbsp;
                <button onClick={this.showData}>点我提示左侧的数据</button>
                &nbsp;
                <input ref={c => this.input2 = c} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
            </div>
        )
    }
}
CreateRef函数形式Ref
class Demo extends React.Component {

    // this.myRef = React.createRef();
    // this.myRef2 = React.createRef();

    showData = () => {
        alert(this.myRef.current.value);
    };

    showData2 = () => {
        alert(this.myRef2.current.value);
    };

    render() {
        return (
            <div>
                <input ref={this.myRef = React.createRef()} type="text" placeholder="点击按钮提示数据"/>
                &nbsp;
                <button onClick={this.showData}>点我提示左侧的数据</button>
                &nbsp;
                <input ref={this.myRef2 = React.createRef()} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
            </div>
        )
    }
}

事件处理

  • 通过onXxx属性指定事件处理函数(注意大小写),React二次封装事件,为了更好的兼容性,更高效
    • React使用的是自定义合成事件,而不是使用的原生DOM事件
    • React中的事件是通过事件委托方式处理的,委托给组件最外层的元素
  • 通过event.target得到发生事件的DOM元素对象 —— 减少使用ref,当事件处理的属性是跟标签相关的,则不需要ref指向,只需要使用event.target
showData2 = (event) => {
    alert(event.target.value);
};

<input  onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>

包含表单的组件分类

非受控组件

现用想取

class Login extends React.Component {
    handleSubmit = (event) => {
        event.preventDefault();
        const {username, password} = this;
        alert(`username: ${username}, password: ${password}`);
    };

    render() {
        return (
            <form action="https://www.baidu.comn" onSubmit={this.handleSubmit}>
                用户名:<input ref={c => this.username = c} type="text" name="username"/>
                &nbsp;
                密码:<input ref={c => this.password = c} type="password" name="password"/>
                &nbsp;
                <button>登录</button>
            </form>
        )
    }
}

ReactDOM.render(<Login/>, document.getElementById('test'));
受控组件

简单理解就是VUE中的v-model双向绑定,就是随着输入维护状态

class Login extends React.Component {

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

    handleSubmit = (event) => {
        const {username, password} = this.state;
        alert(`username: ${username}, password: ${password}`);
    };

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

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

    render() {
        return (
            <form onSubmit={this.handleSubmit}>
                用户名:<input onChange={this.saveUsername} type="text" name="username"/>
                &nbsp;
                密码:<input onChange={this.savePassword} type="password" name="password"/>
                &nbsp;
                <button>登录</button>
            </form>
        )
    }
}
高级函数和柯里化

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

  • 若A函数,接受的参数是一个函数
  • 罗A函数,调用的返回值依然是一个函数

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

对于在受控组件中的例子,可以使用高阶函数和柯里化技术进行精简化处理

class Login extends React.Component {

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

    handleSubmit = (event) => {
        const {username, password} = this.state;
        alert(`username: ${username}, password: ${password}`);
    };

    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"/>
                &nbsp;
                密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>
                &nbsp;
                <button>登录</button>
            </form>
        )
    }
}

如果不适用柯里化技术,还可以如下写法

saveFormDataSecond = (dateType, event) => {
    this.setState([dateType], event.target.value)
};

用户名:<input onChange={event => this.saveFormDataSecond('username', event)} type="text" name="username"/

生命周期

引出生命周期的例子

若要在render()中使用计时器,会因为render()函数的性质而生成指数级个计时器,占用大量CPU

// 初始化会调用一次,后续每次更新状态都会重新调用一次,共1+n次
// 因此计时器会指数级开启
render() {
    setInterval(() => {
        let {opacity} = this.state;
        opacity -= 0.1
        if (opacity <= 0) opacity = 1;
        // 设置新的透明度
        this.setState({opacity})
    }, 200);
    return (
        <div>
            <h2 style={{opacity: this.state.opacity}}>React学不会怎么办?</h2>
            <button onClick={this.death}>不活了</button>
        </div>
    )
}

为了在组件挂载前调用后只调用一次函数,因此使用到生命周期中的componentDidMount()函数

// 组件挂载完毕之后调用
componentDidMount() {
    setInterval(() => {
        let {opacity} = this.state;
        opacity -= 0.1;
        if (opacity <= 0) opacity = 1;
        // 设置新的透明度
        this.setState({opacity})
    }, 200);
}

但是在点击button调用death()函数后,虽然会清除计时器和卸载组件,但是调试器会出现错误,这是因为此时状态发生了变化,就会调用render()函数,但是render()函数中的组件已经卸载了,所以同样使用到生命周期中的componentWillUnmount()函数

// 组件将要卸载
componentWillUnmount() {
    // 清楚计时器
    clearInterval(this.timer);
}
生命周期图(旧)

暂时无图

挂载时的生命周期(旧)
class Count extends React.Component {
    
    // 构造器
    constructor(props) {
        console.log('count-constructor');
        super(props);
        this.state = {count: 0};
    }

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

    render() {
        console.log('count-render');
        const {count} = this.state;
        return (
            <div>
                <h2>当前求和为{count}</h2>
                <button onClick={this.addCount}>点我+1</button>
            </div>
        )
    }

    // 组件完成挂载的钩子
    componentDidMount() {
        console.log('count-componentDidMount');
    }
    
    // 组件将要卸载的钩子
    componentWillUnmount() {
        console.log('count-componentWillUnmount');
    }

    // 加1按钮的回调函数
    addCount = () => {
        const {count} = this.state;
        // 更新状态
        this.setState({
            count: count + 1
        })
    };
}
组件更新的生命周期(旧)

更新阶段由组件内部this.setState或者父组件重新render触发

// 控制组件更新的阀门
// 需要有返回值且返回值为true或者false,如果不写系统也会有该函数,且默认返回true
shouldComponentUpdate() {
    console.log('count-shouldComponentUpdate');
    return true;
}

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

// 组件更新完毕的钩子
componentDidUpdate() {
    console.log('count-componentDidUpdate');
}
  • shouldComponentUpdate()函数需要有返回值且返回值为true或者false,如果不写系统也会有该函数,且默认返回true
  • 在执行forceUpdate()函数的过程中,不会调用shouldComponentUpdate()函数。所谓强制更新,就是不更改任何状态中的数据。
生命周期图(新)

暂时无图,后续更新

比较旧生命周期图:

废弃:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

添加:

  • getDerivedStateFromProps

    新的静态 getDerivedStateFromProps 生命周期方法在组件实例化之后以及重新渲染之前调用。它可以返回一个对象来更新 state,或者返回 null 来表示新的 props 不需要任何 state 的更新。

    componentDidUpdate 一起,这个新的生命周期涵盖过时的 componentWillReceiveProps 的所有用例。

  • getSnapshotBeforeUpdate

    新的 getSnapshotBeforeUpdate 生命周期方法在更新之前(如:更新 DOM 之前)被调用。此生命周期的返回值将作为第三个参数传递给 componentDidUpdate。(通常不需要,但在重新渲染过程中手动保留滚动位置等情况下非常有用。)

    componentDidUpdate 一起,这个新的生命周期涵盖过时的 componentWillUpdate 的所有用例。

Key作用

经典面试题:

  1. react/vue中的key作用,内部原理是什么
  2. 为什么遍历列表时,key最好不要用index
  • 虚拟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更新 —— 界面出现问题
    • 如果不存在对数据的逆序添加等破坏顺序操作,仅用于渲染列表用于展示的话是没有问题的
  • 开发中如何选择key
    • 最好使用每条数据的唯一标识作为key
    • 如果确定只是简单的展示数据,用index也是可以的
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值