React(二)- React组件

React系列文章导航

前言

先来说下模块和组件的差别:

模块:向外提供特定功能的JS程序,一般是一个JS文件。
作用:复用JS。

组件:用来实现局部功能效果的代码和资源的集合(Html、Css、Js等)。
作用:复用编码。

安装React调试工具

1.打开谷歌浏览器,选择扩展程序:
在这里插入图片描述
2.点击左侧的扩展程序,并打开Chrome网上应用商店:
在这里插入图片描述
3.搜索React,添加对应组件,注意提供方是Facebook
在这里插入图片描述
4.固定插件于右上角,方便使用:
在这里插入图片描述
5.打开用React框架编写的页面,进行检查,如美团的首页:
在这里插入图片描述

以服务的方式打开HTML文件

有些小伙伴通过Vscode软件打开某个HTML,并且也下载了上面的React调试工具,但是那个图标还是不显示,是因为我们是以本地的方式打开这个文件,而不是打开一个运行于某个服务上的页面。

解决方案如下:
1.下载Live Server插件:
在这里插入图片描述
2.安装完成后,鼠标右键打开文件:
在这里插入图片描述
3.完成,其中默认的端口是5500。
在这里插入图片描述

一. React组件

React的组件有两种:

  • 函数式组件
  • 类式组件

我们从简单的函数式组件来讲起

1.1 函数式组件

案例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>函数式组件</title>
</head>

<body>
    <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
    <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
    <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>

    <div id="test"></div>

    <script type="text/babel">
        // 1. 创建函数式组件,函数式组件的名称必须是大写开头,在React系列一种有提及到
        // 若以大写字母开头,React就会去渲染对应的组件
        function DEMO() {
        	// 此处为undefined,因为经过babel编译后,开启了严格模式
            console.log(this);
            return <h2>我是函数定义的组件</h2>
        }
        // 2.渲染组件到页面,第一个参数需要用标签来修饰
        ReactDOM.render(<DEMO/>, document.getElementById('test'))
    </script>
</body>
</html>

页面效果如下:
在这里插入图片描述
倘若组件名字是小写,则报错:
在这里插入图片描述

那么执行ReactDOM.render()代码后,发生了什么?

  1. React解析组件的标签,找到了xxx组件。
  2. 发现该组件是由函数定义的,随后定义了该函数,将返回的虚拟DOM转化为真实DOM。
  3. 页面渲染对应的DOM,展示。

1.2 类式组件

1.2.1 JS有关类的基本语法

1.定义类、构造、方法。

<script type="text/javascript">
	// 定义一个对象
    class Person {
        // 构造器方法
        constructor(name, age) {
            // 构造器中的this指的是:类的实例对象
            this.name = name
            this.age = age
        }
        // 定义一个一般方法,该方法处于类的原型对象上,供给实例对象使用
        say(){
            console.log(`我是${this.name},年龄是${this.age}`)
        }
    }
    // 创建一个对象
    const p1 = new Person("Tom", 20);
    console.log(p1);
    p1.say();
</script>

结果如下:
在这里插入图片描述
2.继承

class Student extends Person {
    // 若有自己新增的属性,构造器可以这么写
    // 通过super来代替this.xxx=xxx这种重复的代码
    constructor(name, age, grade) {
        super(name, age)
        this.grade = grade
    }
}
const s1 = new Student("Tom", 20, 'msg');

接下来来看下如何使用类式组件:

<body>
    <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
    <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
    <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>

    <div id="test"></div>

    <script type="text/babel">
        // 1.创建类式组件,有两个条件。1.需要继承React内置的类。2.类中有render()方法,且有返回值
        class MyComponent extends React.Component {
            render() {
                return <h2>我是类定义的组件</h2>
            }
        }
        ReactDOM.render(<MyComponent />, document.getElementById('test'))
    </script>
</body>

那么对于类式组件,执行ReactDOM.render()代码后,发生了什么?

  1. React解析组件的标签,找到了xxx组件。
  2. 发现该组件是由类定义的,随后new了一个该类的实例,并通过该实例调用到原型上的render方法。
  3. 将render返回的虚拟DOM转化为真实DOM,渲染页面。

1.3 函数式和类式组件的区别

函数式组件类式组件
定义方式function xxx(){}class xxx extends React.Component{}
this的指向undefined组件实例对象
注意的点一定要有render()方法,并且有返回值
解析过程随后定义了该函数,将返回的虚拟DOM转化为真实DOM随后new了一个该类的实例,并通过该实例调用到原型上的render方法。

二. 组件实例的三大核心

2.1 state

React中的每个组件实例对象都有一个“状态”:state,值是对象(可以包含多个key-value组合),一般我们通过更新组件的state的值来更新对应的页面显示。

注意:

  • 这里的组件实例对象指的是类式组件。

Demo1:通过修改state值,来更新页面

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>JS</title>
</head>

<body>
    <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
    <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
    <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>

    <div id="test"></div>

    <script type="text/babel">
        class Weather extends React.Component {
            // 这里的this指的是组件实例对象
            constructor(props) {
                super(props)
                // 2.初始化状态
                this.state = { isHot: true }
                // 5.解决change方法中的this指向问题
                this.method = this.change.bind(this)
            }
            // 3.定义render方法
            render() {
                // 这里的this指的是组件实例对象
                // React中的点击事件的绑定,需要用到onClick,并且后面的函数,不需要加括号,因为这不是一个赋值语句。而是指定一个函数
                return <h2 onClick={this.method}>今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h2>
            }
            // 4.定义一个绑定事件函数
            change() {
                // 该方法中,只有通过Weather实例对象去调用change()方法的时候,这里面的this才是实例对象
                // 由于change是作为onClick的回调,因此不是通过实例的方式去调用的,而是直接调用。
                // 类中的方法默认开启了局部的严格模式,因此console.log(this)这里输出是undefined
                // console.log(this)

                // 5.类组件中的状态不可以直接修改,而是需要一个内置的方法进行修改
                const isHot = this.state.isHot
                this.setState({isHot:!isHot})
            }
        }
        // 渲染组件到页面
        ReactDOM.render(<Weather />, document.getElementById('test'))
    </script>
</body>

</html>

页面效果:
在这里插入图片描述
点击后:
在这里插入图片描述

注意:

  1. 若鼠标点击N次,那么代码中,构造器只会执行一次,而render()方法会执行1+N次。

Demo2:Demo1的精简写法

<script type="text/babel">
    class Weather extends React.Component {
        // 不用写构造了,直接赋值初始化就行
        state = { isHot: true }

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

        // 箭头函数有一个特点就是没有自己的this,如果箭头函数中使用了this关键字,那么就会去找外层的this来当做自己的this
        // 因此这里的this指的是类组件实例对象
        // 以后一个类组件中的方法一般都要写成赋值语句的格式,并且写成箭头函数
        change = () => {
            const isHot = this.state.isHot
            this.setState({ isHot: !isHot })
        }
    }
    // 渲染组件到页面
    ReactDOM.render(<Weather />, document.getElementById('test'))
</script>

小总结1

  1. 类式组件中,比如绑定一个单击事件,需要用bind函数去解决this的指向问题。
  2. 类式组件中,构造和render()方法中的this指的是组件实例对象,而其他的内部方法的thisundefined
  3. 类组件中的状态不可以直接修改,而是需要一个内置的方法setState()进行修改。
  4. 箭头函数有一个特点就是没有自己的this,如果箭头函数中使用了this关键字,那么就会去找外层的this来当做自己的this
  5. 实际开发中,建议使用Demo2的方式去编写类式组件。state是一个对象,直接内部赋值即可。结构:state = {key:value,key1:value2...}

2.2 props

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

Demo1:props的简单使用:

<div id="test"></div>

<script type="text/babel">
    class Person extends React.Component {
        render() {
            const { name, age, sex } = this.props
            return (
                <ul>
                    <li>姓名:{name}</li>
                    <li>性别:{sex}</li>
                    <li>年龄:{age}</li>
                </ul>
            )
        }
    }
    ReactDOM.render(<Person name="Hello" age="30" sex="男" />, document.getElementById('test'))
</script>

React调试工具结果如下:
在这里插入图片描述
当然,若一个对象的属性有非常多,我们不可能像上面一样将属性一一列出来,因此,React还提供了一个语法糖,如下:

const p = { name: 'Tom', age: 19, sex: '男' }
ReactDOM.render(<Person {...p} />, document.getElementById('test'))

2.2.1 语法糖扩展

语法糖:展开运算符...数组

<script type="text/javascript">
    let arr1 = [1, 2, 3, 4, 5]
    let arr2 = [6, 7, 8, 9]
    console.log(...arr1)

    let arr3 = [...arr1, ...arr2]
    console.log(...arr3)

    function sum(...numbers) {
        // 第一个参数是上一个值,第二个参数是当前值,那么求和就是上面的和+当前值
        return numbers.reduce((pre, cur) => {
            return pre + cur
        })
    }     
    console.log(sum(...arr3))
</script>

结果如下:
在这里插入图片描述
对象的复制操作:

let obj1 = { a: "str", b: "str" }
// 复制一个对象,若想更改某个属性,直接后面跟上key:value即可
let obj2 = { ...obj1 }
// 改变引用
let obj3 = obj1

obj1.a = "a"
console.log(obj2)
console.log(obj3)

结果如下:
在这里插入图片描述

2.2.2 对props进行限制

这是Demo1当中的部分代码:

ReactDOM.render(<Person name="Hello" age="30" sex="男" />, document.getElementById('test'))

我们可以发现,一般年龄这个属性,其字段类型应该是整形,但是我们在传参的时候,把它当做了字符串类型,如果想要传入一个整形,应该这么改:

ReactDOM.render(<Person name="Hello" age={30} sex="男" />, document.getElementById('test'))

但是这并不是最好的一种方案,我们应该对标签属性类型进行限制

引用prop-types.js文件:一旦引用后,全局就多了一个对象PropTypes

<script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>

Demo1:类式组件使用props并定义规则

<body>
    <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
    <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
    <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
    <script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>

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

    <script type="text/babel">
        class Person extends React.Component {
            render() {
                const { name, age, sex } = this.props
                return (
                    <ul>
                        <li>姓名:{name}</li>
                        <li>性别:{sex}</li>
                        <li>年龄:{age + 1}</li>
                    </ul>
                )
            }
        }
        // 定义标签属性规则
        Person.propTypes = {
            name: PropTypes.string.isRequired,// 限制name必传,且为string类型
            say: PropTypes.func// 限制say标签的类型必须是函数
        }
        // 默认值
        Person.defaultProps = {
            sex: '不男不女'
        }

        ReactDOM.render(<Person name={30} age={30} sex="男" say="2"/>, document.getElementById('test'))
        ReactDOM.render(<Person age={30} sex="男" />, document.getElementById('test1'))
        ReactDOM.render(<Person name="Str" age={30} />, document.getElementById('test2'))
    </script>
</body>

结果如下:
在这里插入图片描述

Demo2:Demo1的精简写法(将标签规则以及默认值放到类组件中)

<script type="text/babel">
    class Person extends React.Component {
        // 定义标签属性规则
        static propTypes = {
            // ...
        }
        // 默认值
        static defaultProps = {
            // ...
        }
        render() {
            //...
        }
    }
</script>

2.2.3 类式组件和函数式组件的构造与props

官网中有提到,通常在React中,构造函数仅用于以下两种情况:

  • 通过this.state赋值对象来初始化内部state
  • 为事件处理函数绑定实例。

注意:

1.在构造函数里面,不要使用setState()方法,若要初始化,直接this.state=xxx即可。
2.若Reat.Component的子类有构造器,若构造器中用到了this.props,那么必须往构造器中传入props,且必须调用super()
3.props是只读的属性,修改值相关的操作会报错。


函数式组件使用props

<div id="test"></div>
<script type="text/babel">
    function Person(props) {
        const { name, age, sex } = props
        return (
            <ul>
                <li>姓名:{name}</li>
                <li>性别:{sex}</li>
                <li>年龄:{age + 1}</li>
            </ul>
        )
    }
    Person.propTypes = {
        name: PropTypes.string.isRequired,
    }
    // 默认值
    Person.defaultProps = {
        sex: '男',
        age: 18
    }
    ReactDOM.render(<Person name="小明" />, document.getElementById('test'))
</script>

小总结2

  1. 每个组件标签的所有属性都会保存到props中。
  2. 往组件中传入属性的时候,可以通过{...对象}展开运算符的方式来传参。
  3. props的属性限制需要引入prop-types.js文件。规则格式:xxx.propTypes={},默认值格式xxx.defaultProps={}
  4. props是只读的属性,修改值相关的操作会报错。

2.3 refs

Demo1:点击按钮,提示第一个输入框中的值,点击第二个输入框失去焦点时,提示输入框中的内容。

<body>
    <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
    <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
    <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>

    <div id="test"></div>
    <script type="text/babel">
        // 创建组件
        class Demo extends React.Component {
            render() {
                return (
                    <div>
                        <input ref="p1" type="text" placeholder="点击按钮提示数据" />&nbsp;&nbsp;
                        <button ref="b" onClick={this.showData}>点击我提示左侧的数据</button>&nbsp;&nbsp;
                        <input onBlur={this.showData2} ref="p2" type="text" placeholder="失去焦点提示数据" />
                    </div>
                )
            }
            // 展示左侧输入框的数据
            showData = () => {
                const { p1 } = this.refs
                alert(p1.value)
            }
            // 展示右侧输入框的数据
            showData2 = () => {
                const { p2 } = this.refs
                alert(p2.value)
            }
        }
        // 渲染组件到页面
        ReactDOM.render(<Demo />, document.getElementById('test'))
    </script>
</body>

结果如下:
在这里插入图片描述
其实这种refs的使用方式并不好,上述案例中是string类型的,而该类型的refs存在一些问题。官方建议用回调函数或者createRef API的方式去代替。

2.3.1 回调方式使用refs

render() {
    return (
        <div>
            // 将当前的节点赋给this对象,并且命名为input1
            <input ref={(curNode) => this.input1 = curNode} type="text" placeholder="点击按钮提示数据" />&nbsp;&nbsp;
            <button ref="b" onClick={this.showData}>点击我提示左侧的数据</button>&nbsp;&nbsp;
        </div>
    )
}
// 展示左侧输入框的数据
showData = () => {
    const { input1 } = this
    alert(input1.value)
}

其中,回调ref这种方式还有一个问题需要考虑:回调执行次数,如果ref回调函数是以内联函数的方式去定义的(如上面的案例中,直接在标签里面定义了一个函数),那么在更新过程中他会被执行两次:

  1. 第一次传入参数null。
  2. 第二次传入参数DOM元素。

这是因为在每次渲染时会创建一个新的函数实例,所以 React会清空旧的 ref 并且设置新的。

案例如下:

<script type="text/babel">
    // 创建组件
    class Demo extends React.Component {
        state = { isHot: true }
        render() {
            const { isHot } = this.state
            return (
                <div>
                    <h2>今天天气很{isHot ? '炎热' : '凉爽'}</h2>
                    <input type="text" ref={(curNode) => { this.input1 = curNode; console.log('@', curNode) }} />
                    <button onClick={this.showInfo}>点我提示输入的数据</button>
                    <button onClick={this.change}>切换</button>
                </div>
            )
        }
        showInfo = () => {
            const { input1 } = this
            alert(input1.value)
        }
        change = () => {
            const { isHot } = this.state
            this.setState({ isHot: !isHot })
        }
    }
    // 渲染组件到页面
    ReactDOM.render(<Demo />, document.getElementById('test'))
</script>

结果如下:可以发现,每点击一次切换按钮,就会调用2次内联函数
在这里插入图片描述

那么如何避免这种问题的发生呢?

解决:通过将 ref 的回调函数定义成 class 的绑定函数的方式。
改动部分如下:

render() {
    const { isHot } = this.state
    return (
        <div>
            <h2>今天天气很{isHot ? '炎热' : '凉爽'}</h2>
            {/*<input type="text" ref={(curNode) => { this.input1 = curNode; console.log('@', curNode) }} />*/}
            <input type="text" ref={this.saveInput} />
            <button onClick={this.showInfo}>点我提示输入的数据</button>
            <button onClick={this.change}>切换</button>
        </div>
    )
}
saveInput = (curNode) => {
    this.input1 = curNode; console.log('@', curNode)
}

最后无论点击多少次切换按钮,都不会多次调用函数:
在这里插入图片描述
这两种本质上是没有区别的,内联函数产生的2次调用也不会有什么影响,因此没有必要特意将内联函数的写法改成第二种。

2.3.2 createRef方式使用refs

案例如下:

class Demo extends React.Component {
	 /**
	  * React.createRef()调用后会返回一个容器,该容器可以存储被ref所标识的节点。
	  * 但是该容器是只能够存储一个节点。
	 */
	 myRef = React.createRef()
	 render() {
	     return (
	         <div>
	             <input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />&nbsp;&nbsp;
	             <button ref={this.myRef} ref="b" onClick={this.showData}>点击我提示左侧的数据</button>&nbsp;&nbsp;
	         </div>  
	     )
	 }
	 // 展示左侧输入框的数据
	 showData = () => {
	     // current指的是当前节点,也就是<input xxx>框 那么自然而然的current.value也就是输入框的值了
	     console.log(this.myRef.current.value)
	 }
}

小总结3

  1. refs引用:可以将带有ref="xxx"标识的所有标签给当前组件实例对象中的refs属性。
  2. 官方不建议使用string类型的ref引用,推荐使用回调函数或者createRef API
  3. 若是回调函数使用,使用格式ref="内联函数"但是这种方式在更新操作时会造成2次调用,第一次传入参数null,第二次传入DOM元素。(实际并不会影响到什么)
  4. API方式,则可以通过var xxx = React.createRef()来返回一个容器,容器用来存储被ref标识的节点,但是只能对应一个节点。 可以通过this.xxx.current.value来获得当前节点的值(比如输入框)
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Zong_0915

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

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

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

打赏作者

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

抵扣说明:

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

余额充值