02_React面向组件编程(组件的使用和三大核心属性)

目录

一、组件的使用

函数式组件

1.1  Functions are not valid as a React child.

1.2  The tag is unrecognized in this browser.

类式组件

1.  类相关知识

2.  类式组件 

二、组件实例的三大核心

2.1  state 状态

通过更新组件的 state 来更新对应的页面显示(重新渲染组件)。

2.1.1  点击事件的三种写法

2.1.2  事件的使用

        1.  Invalid event handler property `onclick`. Did you mean `onClick`?

        2.  Expected `onClick` listener to be a function, instead got a value of `string` type.

        3.  未点击就有值输出

        4.  change is not defined

        5.  cannot read properties of undefined (reading 'state')

2.1.3  内容的修改 (setState)

2.1.4  简写方式

2.2  props

通过标签属性从组件外向组件内传递变化的数据

2.2.1  展开运算符

        语法规则:

        1. 函数调用

        2. 字面量数组构造或字符串

        3. 构造字面量对象时,进行克隆或者属性拷贝

2.2.2  React组件标签中可以用展开运算符展开

2.2.3  对props中的属性值进行类型限制和必要性限制

2.3  refs

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

1. 字符串形式的ref

2.  回调形式的ref

3. createRef创建ref容器


一、组件的使用

函数式组件

1.1  Functions are not valid as a React child.

原因:函数作为react子函数无效。

解决方案:尝试渲染一个真正的组件,确保将其用作<Component /> 而不是Component

1.2  The tag <demo> is unrecognized in this browser.

原因:我们使用一个在浏览器中不存在的标签或以小写字母开头的组件名称

解决方案:使用有效的标签名称,并将组件的第一个字母大写

<!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>函数式组件</title>
</head>
<body>
    <div id="test">

    </div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>

    <script type="text/babel">
       function Demo(){
        //严格模式,禁止自定义函数中的this 指向window
        console.log(this)//undefined,因为babel编译后开启了严格模式
        return <h2>函数式组件</h2>
       }
       //渲染组件到页面
       //Functions are not valid as a React child. ==> <demo/>
       //The tag <demo> is unrecognized in this browser.  ==><Demo/>
       ReactDOM.render(<Demo/>,document.getElementById('test'))
    </script>
</body>
</html>

补充

  • babel编译后开启了严格模式
  • 严格模式,禁止自定义函数中的this 指向window

工作流程:

  • React解析了组件标签,找到了Demo组件
  • 发现组件是使用函数定义,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中

类式组件

1.  类相关知识

<!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>Document</title>
</head>

<body>
    <script type="text/javascript">
        //创建一个person 类
        class Person {
            //构造器方法
            constructor(name, age) {
                //构造器中的this,是类的实例对象
                this.name = name
                this.age = age
            }

            //一般方法
            //say类的原型对象上,供实例使用
            //通过person实例调用say时,say中的this就是Person实例
            say() {
                console.log(`我叫 ${this.name} ,今年 ${this.age} `)
            }
        }
        //继承
        class Student extends Person {
            //Must call super constructor in derived class before accessing 'this' or returning from derived constructor
            constructor(name, age, grade) {
                //super必须在最开始调用
                super(name, age);
                this.grade = grade
            }
            //重写从父类继承的方法
            say() {
                console.log(`我叫 ${this.name} ,今年 ${this.age},我读的是 ${this.grade} `)
            }

            //say类的原型对象上,供实例使用
            //通过Student实例调用speak时,speak中的this就是Student实例
            speak() {
                console.log('hello')
            }
        }
        const p1 = new Person('tom', 18)
        const p2 = new Person('nancy', 14)
        console.log(p1)
        p1.say();
        //call更改this指向
        p1.say.call({
            a: 1,
            b: 2
        }); //我叫 undefined ,今年 undefined

        const s1 = new Student('nan', 14, '高一')
        console.log(s1)
        s1.say()
        s1.speak()
    </script>
</body>

</html>

2.  类式组件 

<!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>类式组件</title>
</head>

<body>
    <div id="test">

    </div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>

    <script type="text/babel">
        //必须继承React中的组件
        class MyComponent extends React.Component {
            render() {
                //MyComponent类的原型对象上,供实例使用
                //render中的this是 MyComponent的实例对象 <=> yComponent组件实例对象
                console.log(this);
                return <h2>类式组件</h2>
            }
        }

        //渲染组件
        ReactDOM.render(<MyComponent/>,document.getElementById('test'))
    </script>
</body>

</html>

工作流程:

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

二、组件实例的三大核心

2.1  state 状态

  • state是最重要的属性,值是对象(key:value形式)
  • 通过更新组件的 state 来更新对应的页面显示(重新渲染组件)。

案例  点击页面,数据内容变更

<!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>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>

    <script type="text/babel">
        class MyComponent extends React.Component {
            //初始化状态
            state = { isHot: true, wind: '微风' }
    
            render() {
                const { isHot, wind } = this.state;           
                return <h2 onClick={this.change}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h2>
            }

            //修改状态
            change = ()=> {
                const changeHot = this.state.isHot
                this.setState({
                    isHot: !changeHot
                })
            }
        }

        //渲染组件
        ReactDOM.render(<MyComponent />, document.getElementById('test'))
    </script>
</body>

</html>

重难点解析

  • 创建复杂的组件时,必须必须继承React中的组件 :class MyComponent extends React.Component

2.1.1  点击事件的三种写法

第一种

const title = document.getElementById('title');
title.addEventListener('click',()=>{
      console.log(1);
})

 第二种

title.onclick = ()=>{
      console.log(2);
}

 第三种

function change() {
    console.log(3);
}

注意:一般用第三种函数体形式,且 将其写在clss中,需要去除 function 关键字

change() {

       console.log(3);

}

2.1.2  事件的使用

<h2 οnclick="change()">今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h2>

  • 报错内容

1.  Invalid event handler property `onclick`. Did you mean `onClick`?

报错原因:React 事件的命名采用小驼峰式(camelCase),而不是纯小写

解决方案:改成 onClick

2.  Expected `onClick` listener to be a function, instead got a value of `string` type.

报错原因:期望’ onClick ‘侦听器是一个函数,而不是得到一个’ string '类型的值

解决方案:onClick= {change()}

3.  未点击就有值输出

错误原因:change() 调用函数,将其返回值 undefined 交给 onClick 作为回调

解决方案: onClick= {change}

4.  change is not defined

错误原因:方法是在类的原型对象上,供实例调用,

解决方案:render(){ } 的 this 指向实例对象,所以 onClick指定回调时候 需要写成 this.change 

5.  cannot read properties of undefined (reading 'state')

错误原因:因为change作为onClick的回调,从堆里找到函数直接调用,而不是通过实例调用。而且类中所有定义的方法,默认局部开启了严格模式,所以不敢指向window , change中的 this为 undefined

解决方案:

class MyComponent extends React.Component {

         // 构造器执行了一次 (有几个组件实例,<MyComponent />)

        constructor(props) {

              //this ==> 组件的实例对象

              //missing super() call in constructor

              //constructor中一定要有super

              super(props); 

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

                        1. 通过给 this.state 赋值对象来初始化内部 state。

                         2. 为事件处理函数绑定实例

                */

              this. state = { isHot: true, wind: '微风' }

              //  bind 生成新的函数

              //  实例自身的 demo 属性  =  原型上的change(MyComponent实例对象        

              this.demo = this.change.bind(this)

      }

         // render执行了 1 + n (1是初始化,n是状态更新的次数)

        render() {

                //this ==> 组件的实例对象

                //读取状态

                const { isHot, wind } = this.state;          

                return <h2 onClick={this.demo}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h2>

        }

        // change调用几次就执行几次,onClick

        change() {

              // 未bind 前 this ==> undefined !!!

              // console.log(this);

                const changeHot = this.state.isHot

                this.setState({

                    isHot: !changeHot

                })

        }

}

//渲染组件

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

2.1.3  内容的修改 (setState)

状态不能直接更改,要借助内置API去更改。setState 进行更新,更新是一种合并,不是直接替换

change ()  {
      const changeHot = this.state.isHot
      this.setState({
         isHot: !changeHot
     })
}

2.1.4  简写方式

  • 初始化状态,写成赋值语句形式

给实例对象添加一个属性state,值为引用类型的对象类型

 state = { isHot: true, wind: '微风' }
  • 自定义方法都要写成 :赋值语句的形式 + 箭头函数

给实例对象添加一个属性change,值为引用类型的函数类型

//this.change = this.change.bind(this)

changeIsHot = function(){
        const changeHot = this.state.isHot
        this.setState({
               isHot: !changeHot
         })

}  

报错内容:cannot read properties of undefined (reading 'state')

报错原因:this指向问题,原先在原型对象上,现在在实例对象自身

解决方案:在箭头函数中使用this,往外找this。this指向声明时所在作用域下this 的值, 也就是 MyComponent的实例对象

  • 去掉构造器

2.2  props

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

案例  自定义一个显示人员信息的组件,并对信息做出限制

  • 姓名必填,且为字符串类型
  • 性别默认为男,字符串类型
  • 年龄为数字类型
  • 年级默认为高三,字符串类型

<!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>props</title>
    <style>
        .txt {
            color: blue;
        }
    </style>
</head>

<body>
    <div id="test1">

    </div>
    <div id="test2">

    </div>
    <div id="test3">

    </div>
    <!-- React -->
    <script src="../../js/react.development.js"></script>
    <!-- ReactDOM -->
    <script src="../../js/react-dom.development.js"></script>
    <script src="../../js/babel.min.js"></script>
    <!-- 用于对组件标签属性进行限制 PropTypes-->
    <script src="../../js/prop-types.js"></script>

    <script type="text/babel">
        class Person extends React.Component {
            //初始化状态
            state = {}

            // 对标签属性 类型、必要性的限制
            // static 给类本身添加属性
            static propTypes = {
                name: PropTypes.string.isRequired,
                age: PropTypes.number,
                sex: PropTypes.string,
                garade: PropTypes.string,

                speak: PropTypes.func
            }

            // 指定默认属性
            static defaultProps = {
                sex: "男",
                garade: '高三'
            }

            render() {
                const { name, age, garade, sex } = this.props;
                //props 只读
                //cannot assign to read only property 'name' of object '#<Object>'
                //不能在只读的属性 name  上做出修改
                //this.props.name = "test"

                //必须有返回值
                return (
                    <ul>
                        <li>姓名:<span className="txt" style={{ fontSize: '20px' }}>{name}</span></li>
                        <li>年龄:<span className="txt">{age + 1}</span></li>
                        <li>性别:<span className="txt">{sex}</span></li>
                        <li>年级:<span className="txt" style={{ backgroundColor: 'orange' }}>{garade}</span></li>

                    </ul>

                )
            }

        }
        function speak(){
            console.log('hello!');
        }
        const p = {
            name: 'Nancy',
            age: 17,
            garade: '高三'
        }
        //console.log('@', ...p);//@
        //渲染组件
        //对 props 进行限制
        ReactDOM.render(<Person {...p} speak={speak} />, document.getElementById('test1'))
        //failed prop type: Ivalid prop `name` of type `number` supplied to `Person`, expected `string`.
        ReactDOM.render(<Person name='西西' age={18} />, document.getElementById('test2'))

        //The prop `name` is marked as required in `Person`, but its value is `undefined`.
        ReactDOM.render(<Person name='丽丽' age={16} sex="女" />, document.getElementById('test3'))
    </script>
</body>

</html>

重难点解析

  • 通过标签属性从组件外向组件内传递变化的数据

2.2.1  展开运算符

可以在函数调用/数组构造时,将数组表达式或者 string 在语法层面展开;还可以在构造字面量对象时,将对象表达式按 key-value 的方式展开。 实际上,展开语法和 Object.assign() 行为一致,执行的都是浅拷贝 (只遍历一层)。

语法规则:

1. 函数调用

function sum(...nums) {
    return nums.reduce((pre,cur)=>{
         return pre + cur
     })
}
console.log(sum(1,2,3,4));

补充:

reduce() 方法对数组中的每个元素按序执行一个由您提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。

2. 字面量数组构造或字符串

let arr1 = [1, 2, 3]
//数组拷贝 (copy)
let arr2 = [...arr1]
let arr3 = [4, 5,3]
arr2.push(4)
// console.log(arr1); //(3) [1, 2, 3]
//console.log(arr2); // (4) [1, 2, 3, 4]
//连接多个数组,创建了一个新的 arr4 数组
let arr4 = [...arr1, ...arr3]
// console.log(arr4); //(6) [1, 2, 3, 4, 5, 3]

3. 构造字面量对象时,进行克隆或者属性拷贝

  • 浅拷贝,你变我也变

  • 深拷贝,复制对象,属性合并及替换

let person1 = {
   name: 'Nancy',
   age: 17,
   class: '高三'
}
//console.log(...person);//报错,展开运算符不能展开一个对象
let person2 = person1;//浅拷贝,你变我也变
let person3 = {
    //深拷贝,复制对象,属性合并及替换
    ...person1,
    name: 'Jack',
    sex :'男'
}; 

person1.name = 'xxx'
//console.log(person1);//{name: 'xxx', age: 17, class: '高三'}
//console.log(person2);//{name: 'xxx', age: 17, class: '高三'}
//console.log(person3);//{name: 'Jack', age: 17, class: '高三', sex: '男'}

2.2.2  React组件标签中可以用展开运算符展开

  • 将组件的标签属性,批量传递到组件实例对象的props属性上

function speak(){
     console.log('hello!');
}
const p = {
      name: 'Nancy',
      age: 17,
      garade: '高三'
}

ReactDOM.render(<Person {...p}  speak={speak}/>, document.getElementById('test1'))

2.2.3  对props中的属性值进行类型限制和必要性限制

  • 使用prop-types库进限制(需要引入prop-types库)

        //React propTypes 给类自身属性加规则
        //对标签属性 类型,必要性的限制
        Person.propTypes = {
            //name:React.PropTypes.string 15.xxx.xx
            name: PropTypes.string.isRequired,
            age: PropTypes.number,
            sex: PropTypes.string,
            garade: PropTypes.string,
            //函数
            //Person: prop type `speak` is invalid; it must be a function, usually from the `prop-types` package, but received `undefined`.
            speak:PropTypes.func
        }
        //默认值设置
        Person.defaultProps = {
            sex: "男",
            garade: '高三'
        }

注意:

1. 使用 pro-type 库,对组件标签属性进行限制

<script src="../../js/prop-types.js"></script>

官方文档 : 使用 PropTypes 进行类型检查 | React中文网

组件特殊属性

  • propTypes ,进行类型检查与必要性限制
  • defaultProps ,定义props的默认值

React中类型的写法,其中函数类型为 func

  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

2. props 的只读性,不能修改自身的 props

this.props.name = "test"

报错内容:cannot assign to read only property 'name' of object '#<Object>'

报错原因:不能在只读的属性 name  上做出修改

2.3  refs

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

案例  自定义组件

  • 点击按钮, 提示第一个输入框中的值
  • 点击按钮,切换天气
  • 当第2个输入框失去焦点时, 提示这个输入框中的值

1. 字符串形式的ref

showData = () => {

                //收集后的属性refs

                const { input1 } = this.refs;

                alert(input1.value)

                //console.log(this.refs.input1);//真实DOM

                //debugger;

 }

 <input ref="input1" type="text" placeholder="点击按钮提示数据" />

2.  回调形式的ref

 showData = () => {

        const { input1 } = this;

        alert(input1.value);

 }

//类绑定函数

saveInput = (c)=>{

        this.input1 = c;

        console.log("@", c);

}

  •  内联回调
    • 将节点放在组件实例自身上,名字为input1
    • 箭头函数没有this,往外找。render的this是组件的实例对象
<input ref={c => { this.input1 = currentNode; console.log("@", c); }} type="text" placeholder="点击按钮提示数据" />
  •  类绑定函数
<input ref={this.saveInput} type="text" placeholder="点击按钮提示数据" />

注意:回调函数执行几次?

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

3. createRef创建ref容器

//React.createRef调用后返回一个 容器 ,该容器可以存储被 ref所标识的节点

myRef = React.createRef();

//ref={this.myRef} 只能出现一次,专人专用。否则后放入就把之前的给顶掉了

<input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />

 完整代码

<!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>Document</title>
</head>

<body>
    <div id="test1">

    </div>

    <!-- React -->
    <script src="../../js/react.development.js"></script>
    <!-- ReactDOM -->
    <script src="../../js/react-dom.development.js"></script>
    <script src="../../js/babel.min.js"></script>
    <!-- 用于对组件标签属性进行限制 PropTypes-->
    <script src="../../js/prop-types.js"></script>


    <script type="text/babel">
        class Demo extends React.Component {
            state = { ishot: false }

            //React.createRef调用后返回一个 容器 ,该容器可以存储被 ref所标识的节点
            myRef = React.createRef();
            showData = () => {
                //console.log(this.myRef);//{current: input}
                //专人专用,后放入就把之前的给顶掉了 ref={this.myRef} 只能出现一次
                alert(this.myRef.current.value)
            }
            
            //event:发生事件的事件对象
            //发生事件的元素正好是需要操作元素,可以省略ref
            showData2 = (event) => {
               //console.log(event.target);//当前input节点
               alert(event.target.value)
            }

            change = () => {
                const { ishot } = this.state;
                this.setState({
                    ishot: !ishot
                })
            }

            //类绑定函数
            saveInput = (c)=>{
                this.input1 = c; 
                console.log("@", c);
            }

            render() {
                const { ishot } = this.state;
                return (
                    <div>
                        <h3>{ishot ? '炎热' : '凉爽'}</h3>
                        {/*<input ref="input1" type="text" placeholder="点击按钮提示数据" />*/}
                        {/*<input ref={c => { this.input1 = currentNode; console.log("@", c); }} type="text" placeholder="点击按钮提示数据" />*/}
                        {/*<input ref={this.saveInput} type="text" placeholder="点击按钮提示数据" />*/}
                        <input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
                        <button onClick={this.showData}>点击提示左侧数据</button>
                        <button onClick={this.change}>点击切换天气</button>
                        <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
                    </div>
                )

            }
        }
        ReactDOM.render(<Demo />, document.getElementById('test1'))
    </script>
</body>

</html>

 注意:发生事件的元素正好是需要操作元素,可以省略ref

//event:发生事件的事件对象
showData2 = (event) => {
         //console.log(event.target);//当前input节点
         alert(event.target.value)
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

才不吃胡萝卜嘞

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

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

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

打赏作者

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

抵扣说明:

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

余额充值