React-学习笔记(2-组件-组件的三个属性state、props、refs)

本文详细介绍了React中函数式组件和类组件的使用,包括state和props的管理,事件处理中的this指向问题,以及如何解决和更新state。同时,讲解了JavaScript类的构造器、方法和继承。另外,还讨论了类组件中的refs属性,用于获取真实DOM节点。
摘要由CSDN通过智能技术生成

目录

1、函数式组件的简单使用

2、类式组件的简单使用

3、关于JavaScript 类 的简单复习

4、类组件实例属性—— state

(4-1)state基本使用 AND 绑定事件

(4-2)类中方法的 this 指向问题背景

(4-3)解决类中 this 在非实例调用时为 undefined 的问题 AND 完善事件处理 AND 更新state对象中的属性并重新渲染

(4-4)state简化的写法

(4-5)setState修改状态的两种写法

5、类组件实例属性—— props

(5-1)props基本使用

(5-2)props 简写方式

(5-3)对组件标签中设置的属性值做限制(16版本后写法)

 6、函数式组件使用 props

7、类组件实例属性—— refs

(7-1)String 形式(过时的API,面临移除风险)

(7-2)使用内联回调函数形式

(7-3)使用类绑定函数的形式

(7-4)使用 React.createRef()


1、函数式组件的简单使用

// 声明一个简单的函数式组件,返回一段简单的jsx标签内容
// 函数组件内的 this 指向的是undefined,因为babel编译后开启了严格模式
const TextComponent = ()=>{
    return <h1>简单函数组件的使用</h1>
}

// 渲染到页面中
ReactDOM.render(<TextComponent/>, document.querySelector('#test'));
// 使用组件时,第一个参数要写的是组件标签的形式,
// 首字母要大写,标签要闭合,标签名与对应的函数组件的函数名一致
// React 会调用该函数将返回的虚拟dom转换成真实的dom,然后渲染到页面中

2、类式组件的简单使用

// 创建类式组件(类名首字母要大写),要继承 React.Component
class MyComponent extends React.Component{
    // 必须要有一个render实例方法,该方法返回需要渲染的内容
    render(){
        return <h1>这是一个类组件</h1>
    }
}

// React 解析组件标签,会根据名称找到对应的类,然后会构造出实例,
// 并调用其render方法,获得虚拟dom,然后再转换成真实dom然后再渲染到页面上
ReactDOM.render(<MyComponent/>,document.querySelector('#test'));

3、关于JavaScript 类 的简单复习

// 创建一个 person 类
class Person{
    // 构造函数方法
    constructor(name, age){
        // 添加到 this 上的所有内容都会作用到不同的实例身上
        this.name = name;
        this.age = age;
    }
    // 类中可以有构造器、方法、赋值语句(赋值语句不用声明类型)
    gender = 'man'            
    // 实例方法 定义在类的原型对象上
    speak(){
        console.log(`my name is ${this.name}, I am ${this.age} year old.`);
    }
    // 类的静态方法 定义到类的身上,要用 类名.静态方法名 调用
    static allSpeak(){
        console.log('person');
    }
    defToString(str){
        console.log(str);
    }
}
// 创建一个子类 Student 继承 Person
class Student extends Person{
    constructor(name, age, grade){
        super(name, age);               // 调用父类的构造方法 如果要使用,要写在构造器的最前面
        this.grade = grade;
    }
    // 重写父类的speak方法
    speak(){
        console.log(`I am a student,I'm in grade ${this.grade}.`);
    }
    // 子类自己的实例方法
    defaultSpeak(){
        super.speak();                  // 调用父类原来的speak方法
    }
    setName(name){                      // 修改名字
        this.name = name;
    }
    // 在子类的静态方法中调用父类的静态方法
    static staticCallFatherAllSpeak(str){
        console.log('Student类静态方法中调用父类的静态方法 allSpeak():');
        super.allSpeak();
    }
    // 在子类的实例方法中调用父类的静态方法
    callFatherAllSpeak(str){
        // super.allSpeak();            // 错误
        Person.allSpeak();              // 只能这样
    }
}
// 创建一个 Student类 的实例化对象
// 类实例化时传入的参数会作为构造函数的参数。如果不需要参数,则类名后的括号也可省略,如 const s = new Student;
const s1 = new Student('张三', 12, 6);
s1.speak();                     // 因为子类重写了父类的speak方法,则调用的是子类重写的方法
s1.defaultSpeak();

// s1.allSpeak();               // 错
Person.allSpeak();              // 静态方法要这样调用

s1.defToString(s1.name);

s1.setName('李明');
s1.defToString(s1.name);
s1.callFatherAllSpeak('调用父类的静态方法 allSpeak()');

4、类组件实例属性—— state

(4-1)state基本使用 AND 绑定事件

        创建一个天气类 Weather,该类继承自 React.Component,则该类的实例身上会有 props、refs、state等的属性。

        state 作为一个对象在我们的项目中使用,用于存储类组件内部的数据。

        在类Weather 的构造器中,使用this.state 可以获取到 state。初始时state 为 null,我们在构造器中将 state 定义为对象类型,并为其指定若干属性。

// 创建一个天气类
class Weather extends React.Component{
    // 可以通过props访问外部的数据
    constructor(props){
        super(props);           
        this.state = {          // state为一个对象,储存类内部数据
            isHot: false        // 天气是否炎热
        };
    }
    // 当组件的状态数据(state)改变,会自动调用此方法渲染对应的标记 
    render(){
        // 事件绑定的事件名用驼峰写法,on后面的首字母变成大写,如onblur => onBlur
        // 等号右边写一对花括号加函数名,不要加圆括号,加了就会在渲染时立即执行,且会把函数的返回值给onClick
        // 如果函数没有返回值,则把undefined返回给onClick作为回调,对应的事件没有任何效果产生
        return (
            <h2 onClick={changeWeather}>The weather of today is {this.state.isHot ? 'hot' : 'cool'}.</h2>
        )
    }
}
ReactDOM.render(<Weather/>, document.querySelector('#test'));

// 事件处理
function changeWeather(){
    console.log('click');
}

/**
 * 如果在render方法返回的h2中,绑定点击事件写成 onClick={changeWeather()}
 * 即加了一个括号,而changeWeather函数如上不做更改,则页面加载完后输出一个 “click”,此后点击h2没有任何回应,
 * 因为没有返回任何东西给onClick
 * 如果changeWeather函数的函数体改为 return console.log("click"); 
 * 则把 console.log("click") 返回给onClick,则每次点击h2控制台都会输出 click
*/


(4-2)类中方法的 this 指向问题背景

class Weather extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            isHot: false
        };
    }
    render(){
        return (
            /*
                onClick={this.changeWeather} ,相当于把changeWeather这个函数赋给了onClick
                当点击h2内容时,虽然也调用了该changeWeather方法,但是不是实例调用的,方法中的this指向不会是类的任何一个实例
                因为类中的方法默认开启了严格模式,则this将会是undefined
            */
            <h2 onClick={this.changeWeather}>The weather of today is {this.state.isHot ? 'hot' : 'cool'}.</h2>
            /**
             * 此时的调用相当于:
             *      const w = new Weather();
             *      const clickEvent = w.changeWeather;
             *      clickEvent();       //  这样调用
            */
        )
    }
    changeWeather(str){
        // 如果是通过类的实例调用,则该this就指向该类的实例对象,不管经过多少个中间层(即通过类中的其他方法再调用该方法
        // 不管如何,只要不是类的实例调用的,这里的this都会指向undefined
        console.log(str,this);
    }
    test(str){                  // 该方法作为中间层测试,调用 changeWeather 方法
        this.changeWeather(str);
    }
}
ReactDOM.render(<Weather/>, document.querySelector('#test'));

const w = new Weather();
w.changeWeather('直接调用');
w.test('通过 实例对象.类中的另一个函数 再调用changeWeather()');

 控制台输出的两条结果是一样的:

而此时点击h2元素,this 的指向是undefined。(第一个对象是所触发的事件对象)

(4-3)解决类中 this 在非实例调用时为 undefined 的问题 AND 完善事件处理 AND 更新state对象中的属性并重新渲染

        在类的构造器中,让目标方法调用 bind() 方法,修改目标方法的 this 指向,并把返回的新函数赋给一个实例方法。需要使用到该目标方法的地方不在调用该目标方法,转而调用的是新的这个实例方法。

class Weather extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            isHot: false
        };
        // 解决非实例对象调用类中方法时,类中方法的 this 的指向问题
        // function.bind(obj) bind方法调用后返回一个新的函数,该函数中的this将是所指定的第一个参数
        this.selfChangeWeather = this.changeWeather.bind(this);
        // 创建实例时 this.changeWeather 方法往上级的父类中逐层查找
        // this.selfChangeWeather 方法是为该实例创建的,将存在于实例自身
    }
    render(){
        return (
            // 事件绑定的是经过修改 this 指向的 selfChangeWeather 方法,而不是 changeWeather
            <h2 onClick={this.selfChangeWeather}>The weather of today is {this.state.isHot ? 'hot' : 'cool'}.</h2>
        )
    }
    changeWeather(){
        // 处理事件
        
        // this.state.isHot = !this.state.isHot;               // state中的属性值直接改是无效的,写法错误

        /**
         * 应调用React.compontent 中的setState方法来修改
         * 传入一个对象作为参数,对象里的属性是需要修改的属性,值是要改成的新值
         * 是关于原 state 的合并,不会影响其他属性
        */
        this.setState({isHot:!this.state.isHot});
    }
}
ReactDOM.render(<Weather/>, document.querySelector('#test'));


        在控制台中创建一个 Weather 类的实例,可查看或验证 selfChangeWeather 和 changeWeather 的所在位置。

(4-4)state简化的写法

class Weather extends React.Component{
    state = {                   // 直接在类中给 state 赋值
        isHot:false,
    }
    render(){
        return (
            <h2 onClick={this.changeWeather}>The weather of today is {this.state.isHot ? 'hot' : 'cool'}.</h2>
        )
    }
    // 这个自定义方法也是一个赋值语句
    // 箭头函数没有自己的this,this会指向外层,则函数内的this指向的就是这个类的实例
    changeWeather = ()=>{
        this.setState({isHot:!this.state.isHot});
    }
}
ReactDOM.render(<Weather/>, document.querySelector('#test'));


(4-5)setState修改状态的两种写法

        setState 用来更新 state 中的内容,是一个异步函数。

        第一种写法:this.setState(obj: stateChange,[func: callback]) 

state = {
    count: 1
}

// 在页面渲染完毕的时候执行
componentDidMount(){
    this.setState({count: this.state.count + 1}, ()=>{
        // 在更新完count和重新渲染完页面之后,查看count的值
        console.log('1:', this.state.count);
    })
    console.log('2:', this.state.count);
}

// 最后输出的结果是
// 2: 1
// 1: 2

         第二种写法:this.setState(func: stateChangeFunc,[func: callback]) 

        第一个参数是一个函数,这个函数会接收到两个参数:state 和 props;这个函数需要返回一个状态更新对象,来修改state。

state = {
    count : 1
}

componentDidMount(){
    this.setState((state, props)=>{
        return {count: state.count +1}
    },()=>{
        console.log(this.state);
    })
}

5、类组件实例属性—— props

(5-1)props基本使用

        在组件标签中设置属性并赋值,将会添加到props对象中,在组件类中就能使用this.props.属性名来获取到对应属性的值。

        注意,props 是只读的,在类中不允许修改。

class City extends React.Component {
    constructor(props){
        // 如果不将接收到的props传递给super,则在构造器中使用this.props的结果可能是undefined
        super(props);
    }

    render(){
        // 拿到 props 中想要的属性,并用其渲染
        // 如果在组件标签中没有设置规定的属性,就会报错,页面渲染不了
        let {province, childrenCity} = this.props;

        return (
            <div>
                <h3>{ province }</h3>
                <ul>
                    {
                        childrenCity.map((e, index)=>{
                            return (
                                <li key={index}>{ e }</li>
                            )
                        })
                    }
                </ul>    
            </div>
        )
    }
}

let province = '广东';
let childrenCity = ['广州','佛山','珠海'];

// 在组件标签中设置的属性会添加到props对象中
ReactDOM.render(<City province={province} childrenCity={childrenCity}/>, 
    document.getElementById('test'));

(5-2)props 简写方式

class City extends React.Component {
    render(){
        // 拿到 props 中想要的属性,并用其渲染
        // 如果在组件标签中没有设置规定的属性,就会报错,页面渲染不了
        let {province, childrenCity} = this.props;

        return (
            <div>
                <h3>{ province }</h3>
                <ul>
                    {
                        childrenCity.map((e, index)=>{
                            return (
                                <li key={index}>{ e }</li>
                            )
                        })
                    }
                </ul>    
            </div>
        )
    }
}

let province = '广东';
let childrenCity = ['广州','佛山','珠海'];

// 合并成一个对象
let cityObj = {province, childrenCity};

// 在组件标签中设置的属性会添加到props对象中
// ReactDOM.render(<City province={province} childrenCity={childrenCity}/>, document.getElementById('test'));
ReactDOM.render(<City {...cityObj}/>, document.getElementById('test'));

        应该注意的是 ...cityObj 并不能对一个对象进行展开,因为 ... 运算符用于展开可迭代的元素,对象默认是不可迭代的。

       

         而在 js 中,可以使用 {...cityObj} 可以完成对一个对象的深拷贝(原对象中属性发生改变,拷贝出来的对象中对应的属性不变)。

        但是在这里(组件标签中),我们写的不是 js,而是 jsx ,{...cityObj} 这对花括号是 jsx 中书写表达式或变量的一个语法,我们真实书写的只是 ...cityObj ,并不是把一个拷贝出来的对象加在组件标签身上。

        而之所以这样写,能够将这个对象中的属性和值对应地添加到 props 身上,是由 babel 和 React 帮我们解决的。

(5-3)对组件标签中设置的属性值做限制(16版本后写法)

        引入一个库(prop-types)用于做 props 的限制,将得到一个全局对象 PropTypes

<script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>
class Person extends React.Component{
    /** 
     * 第一种写法,写在定义类的内部
     * 用static 修饰作为静态变量,直接添加到类的身上(直接赋值没有static 修饰的变量将存在于实例的身上
     */
    // 对变量的类型以及必要性的限制写在类中 propTypes对象中(规定
    static propTypes = {
        name:PropTypes.string.isRequired,               // 字符串类型,必须
        age:PropTypes.number,                           // 数值类型
        speak:PropTypes.func                            // 函数类型
    }
    // 对变量进行默认值的设置,写在类中 defaultProps 对象中(规定
    static defaultProp = {
        gender:'man',
        age:18
    }

    render(){
        const {name, age, gender} = this.props;
        return (
            <div>
                <div>name: {name}</div>
                <div>age: {age}</div>
                <div>gender: {gender}</div>
            </div>
        )
    }
}
    /* 第二种写法,写在定义类的外部 */
    /* 对变量的类型以及必要性的限制写在类中 propTypes对象中(规定 */
// Person.propTypes = {
//     name:PropTypes.string.isRequired,               // 字符串类型,必须
//     age:PropTypes.number,                           // 数值类型
//     speak:PropTypes.func                            // 函数类型
// }
    /* 对变量进行默认值的设置,写在类中 defaultProps 对象中(规定 */
// Person.defaultProp = {
//     gender:'man',
//     age:18
// }

function speak(){
    console.log("Hello");
}

// 如果属性是字符串,可以直接用引号的方式赋值
// 如果是数值类型(age)或者是函数类型(speak)的,需要用一对花括号进行包裹
ReactDOM.render(<Person name='ben' age={23} gender='man' speak={speak}/>,
    document.querySelector('#test'));

 6、函数式组件使用 props

function Person(props){
    return (
        <div>
            <div>name: {props.name}</div>
            <div>age: {props.age}</div>
            <div>gender: {props.gender}</div>
        </div>
    )
}

Person.propTypes = {
    name:PropTypes.string.isRequired,
    age:PropTypes.number
}
Person.defaultProps = {
    gender:'man'
}

ReactDOM.render(<Person name={"张三"} age={21}/>, document.querySelector('#test'));

7、类组件实例属性—— refs

         在 JSX 标签中,用 ref = ‘xxx’ 来标识自己。在类的方法中可以使用 this.refs.xxx 获取到这个标签元素,获取到的是真实的 DOM 元素。

        不应该过度地去使用 ref 。如事件绑定在目标元素A中,需要获得触发事件的目标元素A,则回调函数中的第一个参数即是目标DOM元素,不必再使用 ref 去获取。

(7-1)String 形式(过时的API,面临移除风险)

class InputShow extends React.Component{
    state = {
        inpval:''               // 预设 inpval 的值为空
    };
    render(){
        return(
            <div>
                { /*(jsx中的注释可以这样写) 当input失去焦点时,收集input中输入的内容,并展示到h1中*/ }
                <input ref='inp' onBlur={this.showInp} type="text"/>
                <h1>{this.state.inpval}</h1>
            </div>
        )
    }
    showInp = ()=>{
        // 失去焦点后更新 inpval 的值,更新后,会自动再次渲染页面
        this.setState({
            inpval:this.refs.inp.value
        })
    }
}
ReactDOM.render(<InputShow/>, document.querySelector("#test"));

(7-2)使用内联回调函数形式

        使用内联函数的形式,在更新的时候该函数会执行两次,第一次执行传递的参数是null,用户清空旧的 ref,然后再执行一次,这次传递的参数是当前的DOM结点,用于设置新的 ref。

        可以用类绑定函数的形式来解决这个问题,但是在大多数的时候是无关紧要的。

class InputShow extends React.Component{
    state = {
        inpval:''               // 预设 inpval 的值为空
    };
    render(){
        return(
            <div>
                {
                    // ref 绑定一个回调函数,每次渲染的时候这个回调函数都会被执行
                    // 该回调函数接收到一个参数为当前的结点 currentNode
                    // 利用回调函数将当前结点赋给类实例对象中的变量inpNode
                }
                <input ref={(cn) => {this.inpNode = cn}} onBlur={this.showInp} type="text"/>
                <h1>{this.state.inpval}</h1>
            </div>
        )
    }
    showInp = ()=>{
        // 失去焦点后更新 inpval 的值,更新后,会自动再次渲染页面
        this.setState({
            inpval:this.inpNode.value       // 从实例身上去找这个结点
        })
    }
}
ReactDOM.render(<InputShow/>, document.querySelector("#test"));

(7-3)使用类绑定函数的形式

class InputShow extends React.Component{
    state = {
        inpval:''               // 预设 inpval 的值为空
    };
    render(){
        return(
            <div>
                <input ref={this.setInpNode} onBlur={this.showInp} type="text"/>
                <h1>{this.state.inpval}</h1>
            </div>
        )
    }
    setInpNode = (node)=>{                  // 定义在类中的函数,用于绑定结点
        this.inpNode = node;
    }
    showInp = ()=>{
        // 失去焦点后更新 inpval 的值,更新后,会自动再次渲染页面
        this.setState({
            inpval:this.inpNode.value       // 从实例身上去找这个结点
        })
    }
}
ReactDOM.render(<InputShow/>, document.querySelector("#test"));

(7-4)使用 React.createRef()

class ShowInfo extends React.Component{
    inpA = React.createRef();        // 对应只能绑定一个结点
    inpB = React.createRef();
    render(){
        return (
            <div>
                <input ref={this.inpA} onBlur={this.showInpA} 
                    style={{backgroundColor:'yellow'}} type="text"/>
                <input ref={this.inpB} onBlur={this.showInpB} 
                    style={{border:'2px solid pink'}} type="text"/>
            </div>
        )    
    }
    showInpA = (e)=>{
        // 失去焦点的回调函数会接收到一个参数,即所触发的事件对象e
        // e.target 即触发该事件的对象,
        // 使用 this.inpA.current 获取到该Dom结点
        console.log(e.target === this.inpA.current);
    }
    showInpB = ()=>{
        console.log('B');
    }
}
ReactDOM.render(<ShowInfo/>, document.getElementById('test'));

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bodyHealthy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值