React组件基础

1. 模块化和组件化的区别

  • 模块:向外提供特定功能的js程序, 一般就是一个js文件,达到复用代码的目的。当应用的js都以模块来编写的, 这个应用就是一个模块化的应用。
  • 组件:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等),组件是一个资源的集合,不仅仅包括js文件。当应用是以多组件的方式实现, 这个应用就是一个组件化的应用。

2. react组件介绍

JSX语法中的标签如果以小写字母开头,会被react默认按照html标签来解析,如果html中没有该标签对应的同名元素则报错。

如果以大写字母开头,react就会去渲染对应的组件,如果组件没有定义,则报错。

组件是react的一等公民,使用react就是在使用组件,组件表示页面的部分功能,组合多个组件实现完整页面功能。

3. react组件的两种创建方式

3.1 函数组件

使用JS的函数(或箭头函数)创建的组件叫做函数组件。

  • 函数名称必须以大写字母开头。react以此来区分组件和普通的react元素。使用函数名作为组件标签名
  • 函数组件必须有返回值,表示该组件的结构
  • 如果返回值是null,表示不渲染任何内容
// 函数组件
function Hello() {
    
    
  console.log(this) // undefined
  return (
    <h1>hello</h1>
  )
}

// 渲染
// ReactDOM.render(<Hello />, document.getElementById('root'))

// 使用单标签和双标签都可以
ReactDOM.render(<Hello></Hello>, document.getElementById('root'))
// 使用箭头函数创建组件
const Hello = () => <div>函数组件哦</div>;

如果直接打印this,结果是undefined。原因:在严格模式下,

  • 禁止this指向全局对象
  • 顶层的this指向undefined

babel在转换jsx语法时,会启用严格模式,从图中可以看书jsx语法是React.createElement的语法糖
在这里插入图片描述

当执行ReactDOM.render(<Hello></Hello>, document.getElementById('root'))时发生了什么?

  1. react解析组件标签,找到对应的组件
  2. 发现组件是函数组件,则调用该函数,将返回的虚拟DOM转为真实DOM,然后渲染到页面上

3.2 使用类创建组件

使用ES6的class创建的组件叫做类组件。

  • 类名必须以大写字母开头
  • 类组件应该继承React.Component父类,从而可以使用父类中的属性和方法
  • 类组件必须提供render()方法
  • render()方法必须有返回值,表示组件的结构。如果不想渲染任何内容,也要返回null
// 类组件
class Hello extends React.Component {
    
    
  render() {
    
    
    return (
      <div>这是类组件</div>
    )
  }
}

// 渲染
ReactDOM.render(<Hello/>, document.getElementById('root'))
  1. React解析组件标签,找到了 Component组件。
  2. 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的 render方法,所以render方法中的this指向类的实例。
  3. 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
  4. 如果在类中定义了方法,这些方法是定义在类的原型上的

3.3 用户定义的组件必须以大写字母开头

以小写字母开头的元素代表一个 HTML 内置组件,比如

或者 会生成相应的字符串 ‘div’ 或者 ‘span’ 传递给 React.createElement(作为参数)。大写字母开头的元素则对应着在 JavaScript 引入或自定义的组件,如 会编译为 React.createElement(Foo)。

如果你确实需要一个以小写字母开头的组件,则在 JSX 中使用它之前,必须将它赋值给一个大写字母开头的变量。

4. 抽离组件为独立的JS文件

  1. 创建组件的js文件,如Hello.js
  2. 在Hello.js文件中导入React
  3. 创建组件(函数组件或类组件)
  4. 导出组件
  5. 在index.js中导入组件
  6. 渲染组件

创建Hello.js组件文件

import React from 'react';

class Hello extends React.Component {
    
    
    render() {
    
    
        return (
            <h1>我是组件的标题</h1>
        )
    }
}

// 导出组件
export default Hello

在index.js文件中导入组件

import React from 'react';
import ReactDOM from 'react-dom';

// 导入组件
import Hello from './components/Hello'

// 渲染
ReactDOM.render(<Hello/>, document.getElementById('root'))

5. react事件处理

5.1 事件绑定

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

语法:on + 事件名称 = {事件处理程序},如onClick = {() => {}},react事件采用驼峰命名法,如onMouseEnter

类组件中的事件绑定

class App extends React.Component {
    
    
    handleClick() {
    
    
        console.log('单击事件触发了');
    }

    render() {
    
    
        return (
            <button onClick={
    
    this.handleClick}>点击按钮</button>
        )
    }
}

// 渲染
ReactDOM.render(<App/>, document.getElementById('root'))

函数组件中的事件绑定

function App2() {
    
    

    function handleClick() {
    
    
        console.log('单击');
    }

    return (
        <button onClick={
    
    handleClick}>按钮</button>
    )
}

// 渲染
ReactDOM.render(<App2 />, document.getElementById('root'))

5.2 事件对象

  • 可以通过事件处理程序的参数获取到事件对象
  • react中的事件对象叫做:合成事件(对象)
  • 合成事件:兼容所有的浏览器,无须担心跨浏览器兼容性问题
class App extends React.Component {
    
    
    handleClick(e) {
    
    
      	// 阻止事件的默认行为
        e.preventDefault();
        console.log('单击事件触发了');
    }

    render() {
    
    
        return (
            <a href='https://www.baidu.com' onClick={
    
    this.handleClick}>百度</a>
        )
    }
}

// 渲染
ReactDOM.render(<App />, document.getElementById('root'))

6. 有状态组件和无状态组件

  • 函数组件又叫做无状态组件,函数组件没有自己的状态,只负责数据的静态展示
  • 类组件又叫做有状态组件,类组件有自己的状态,负责更新UI,让页面动起来
  • **状态(state)**就是数据
    在这里插入图片描述

7. React.PureComponent

React.PureComponent 与 React.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate(),而** React.PureComponent 中以浅层对比 prop 和 state 的方式来实现了该函数。**

如果赋予 React 组件相同的 props 和 state,render() 函数会渲染相同的内容,那么在某些情况下使用 React.PureComponent 可提高性能。

注意
React.PureComponent 中的 shouldComponentUpdate() 仅作对象的浅层比较。如果对象中包含复杂的数据结构,则有可能因为无法检查深层的差别,产生错误的比对结果。仅在你的 props 和 state 较为简单时,才使用 React.PureComponent,或者在深层数据结构发生变化时调用 forceUpdate() 来确保组件被正确地更新。

此外,React.PureComponent 中的 shouldComponentUpdate() 将跳过所有子组件树的 prop 更新。因此,请确保所有子组件也都是“纯”的组件。

8. 组件的state和setState

8.1 state

  • 状态state就是数据,是组件内部的私有数据,只能在组件内部使用
  • state的值是对象,表示一个组件中可以有多少数据
  • 通过this.state来获取状态
class App extends React.Component {
    
    
    constructor() {
    
    
        super();
        // 初始化state
        this.state = {
    
    
            count: 0
        }
    }

    render() {
    
    
        return (
            <div>计数器:{
    
    this.state.count}</div>
        )
    }
}

class App2 extends React.Component {
    
    
    // 简化语法初始化state
    state = {
    
    
        count: 0
    }

    render() {
    
    
        return (
            <div>计数器:{
    
    this.state.count}</div>
        )
    }
}

// 渲染
ReactDOM.render(<App2 />, document.getElementById('root'))

8.2 setState()修改状态

  • 状态是可变的
  • 语法:this.setState({要修改的数据})
  • 注意不要直接通过this.state.xxx = yyy修改state中的数据,这是错误的写法
  • setState的作用:1. 修改state 2. 更新UI
  • 当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state。这里的合并是浅合并。
class App2 extends React.Component {
    
    
    // 简化语法初始化state
    state = {
    
    
        count: 0
    }

    add() {
    
    
        console.log(this); // undefined
        this.setState({
    
    
            count: this.state.count + 1
        })
    }

    render() {
    
    
        return (
            <div>
                <h1>计数器:{
    
    this.state.count}</h1>
            
                {
    
    /* 如果直接这样写会报错, add方法内的this指向是undefined*/}
                {
    
    /* <button onClick={this.add}>+1</button> */}
            
                <button onClick={
    
    () => {
    
    
                    this.setState({
    
    
                        count: this.state.count + 1
                    })
                }}>+1</button>
            </div>
        )
    }
}

9. 事件绑定时this的指向

类中的方法默认开启了严格模式,this的指向可能为undefined。如果要让this重新指向当前组件实例,可以使用下边三种方法:

9.1 箭头函数

如果类的方式是通过实例调用的,那么方法中的this就指向当前实例。在绑定监听函数时,如果是这样写<button onClick={this.add}>+1</button>,相当于是将add方法作为点击事件的回调函数。当点击时,是直接取出这个函数执行,并不是通过实例调用的,所以此时this指向是undefined。

class App extends React.Component {
    
    
    state = {
    
    
        count: 0
    }

    add() {
    
    
        this.setState({
    
    
            count: this.state.count + 1
        })
    }

    render() {
    
    
        return (
            <div>
                <h1>计数器:{
    
    this.state.count}</h1>

                <button onClick={
    
    () => this.add()}></button>
            </div>
        )
    }
}

9.2 Function.prototype.bind()

将事件处理程序中的this绑定到当前组件实例。

class App extends React.Component {
    
    
    constructor() {
    
    
        super();
        this.state = {
    
    
            count: 0
        }
        // 在constructor中给事件处理方法绑定this,constructor中的this指向当前组件实例
        // add方法一开始在类的原型上,下边这个操作相当于给组件实例也添加了一个add方法,
        // 当调用add方法的时候,组件实例上的add会覆盖掉类原型上的
        this.add = this.add.bind(this);
    }

    add() {
    
    
        this.setState({
    
    
            count: this.state.count + 1
        })
    }

    render() {
    
    
        return (
            <div>
                <h1>计数器:{
    
    this.state.count}</h1>

                {
    
    /* 这里的onClick就可以正常调用add方法 */}
                <button onClick={
    
    this.add}>+1</button>
            </div>
        )
    }
}

9.3 箭头函数形式的class实例方法

实例属性除了定义在constructor()方法里面的this上面,也可以定义在类的最顶层。当使用直接给一个变量赋值的形式时,该变量是直接定义在组件实例上的。下方代码中给add赋值了一个箭头函数,则add是直接定义在组件实例上,没有在原型上。

  • 利用箭头函数形式的class实例方法
  • 注意:该语法是实验性语法,但是由于babel的存在可以直接使用
class App extends React.Component {
    
    
    constructor() {
    
    
        super();
        this.state = {
    
    
            count: 0
        }
    }

    add = () => {
    
    
        this.setState({
    
    
            count: this.state.count + 1
        })
    }

    render() {
    
    
        return (
            <div>
                <h1>计数器:{
    
    this.state.count}</h1>

                {
    
    /* 这里的onClick可以正常调用add方法 */}
                <button onClick={
    
    this.add}>+1</button>
            </div>
        )
    }
}

10. 表单处理

在一个受控组件中,表单数据是由 React 组件来管理的。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。

10.1 受控组件

  • 受控组件:其值受到react控制的表单元素
  • HTML中的元素是可输入的,也就是有自己的状态。而react中可变状态通常保存在state中,并且只能通过setState()方法来修改
  • react将state与表单元素值value绑定到一起,由state的值来控制表单元素的值
    在这里插入图片描述

处理步骤:

  1. 在state中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
  2. 给表单元素绑定change事件,将表单元素的值设置为state的值(控制表单元素值的变化)

这种操作类似于vue中的双向数据绑定

class App extends React.Component {
    
    
    state = {
    
    
        text: 'hello'
    }

    onInputChange = e => {
    
    
        console.log(e);
        this.setState({
    
    
            text: e.target.value
        })
    }

    render() {
    
    
        return (
            <input type="text" value={
    
    this.state.text} onChange={
    
    this.onInputChange} />
        )
    }
}

10.2 常见受控组件

class App extends React.Component {
    
    
    state = {
    
    
        text: 'hello',
        content: '富文本',
        city: 'bj',
        isChecked: false
    }

    onInputChange = e => {
    
    
        console.log(e);
        this.setState({
    
    
            text: e.target.value
        })
    }

    onTextareaChange = e => {
    
    
        this.setState({
    
    
            content: e.target.value
        })
    }

    onSelectChange = e => {
    
    
        this.setState({
    
    
            city: e.target.value
        })
    } 

    onCheckChange = e => {
    
    
        console.log(e.target.checked);
        this.setState({
    
    
            isChecked: e.target.checked
        })
    }

    render() {
    
    
        return (
            <div>
                {
    
    /* 文本框 */}
                <input type="text" value={
    
    this.state.text} onChange={
    
    this.onInputChange} />

                {
    
    /* 富文本框 */}
                <textarea value={
    
    this.state.content} onChange={
    
    this.onTextareaChange}></textarea>

                {
    
    /* 下拉框 */}
                <select value={
    
    this.state.city} onChange={
    
    this.onSelectChange}>
                    <option value="sh">上海</option>
                    <option value="zz">郑州</option>
                    <option value="bj">北京</option>
                </select>

                {
    
    /* 复选框,有defaultChecked属性,它只在初次渲染的时候起作用 */}
                <input type="checkbox" checked={
    
    this.state.isChecked} onChange={
    
    this.onCheckChange} />
            </div>

        )
    }
}

上边的代码中对于每一个表单控件都有一个事件处理程序,对上述代码进行优化,让所有的表单控件共用一个事件处理程序。

  1. 给表单控件添加name属性,名称与state相同
  2. 根据表单元素类型获取对应的值
  3. 在change事件处理程序中通过[name]来修改对应的state
    在这里插入图片描述

10.3 非受控组件

  • 借助于ref,使用原生DOM方式来获取表单元素
  • ref的作用,获取DOM或组件

使用步骤:

  1. 调用React.createRef()方法创建一个ref对象
  2. 将创建好的ref对象添加到文本框中
  3. 通过ref对象获取到文本框的值
import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
    
    
    constructor() {
    
    
        super();
        this.textRef = React.createRef();
    }

    getText = e => {
    
    
        console.log(this.textRef.current.value);
    }

    render() {
    
    
        return (
            <div>
                <input type="text" ref={
    
    this.textRef} />
                <button onClick={
    
    this.getText}>获取文本框的值</button>
            </div>
        )
    }
}

// 渲染
ReactDOM.render(<App />, document.getElementById('root'))

10.4 小练习

实现发表评论的组件
在这里插入图片描述

import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
    
    
    state = {
    
    
        comments: [
            {
    
     id: 1, name: 'jack', content: '看看' },
            {
    
     id: 2, name: 'xiaoming', content: '看看3' },
            {
    
     id: 3, name: '打断点', content: '看看5' }
        ],
        username: '',
        commentContent: ''
    }

    renderList() {
    
    
        // 研究一下这里this的指向问题
        console.log(this);
        let {
    
     comments } = this.state;
        if (comments.length === 0) {
    
    
            return (<div>暂无评论</div>)
        }

        return (
            <ul>
                {
    
    comments.map(item => {
    
    
                    return (
                        <li key={
    
    item.id}>
                            <h3>评论人:{
    
    item.name}</h3>
                            <p>评论内容:{
    
    item.content}</p>
                        </li>
                    )
                })}
            </ul>
        )
    }

    handleForm = e => {
    
    
        let {
    
     name, value } = e.target;
        this.setState({
    
    
            [name]: value
        })
    }

    pubComment = () => {
    
    
        let {
    
     comments, username, commentContent } = this.state;
        if(!username.trim() || !commentContent.trim()) {
    
    
            return alert('内容不能为空')
        }
        let comment = {
    
    
            id: comments.length + 1,
            name: username,
            content: commentContent
        }
        comments.unshift(comment)
        this.setState({
    
    
            comments,
            commentContent: '',
            username: ''
        })
    }

    render() {
    
    
        return (
            <div>
                <div>
                    <input type="text" name="username" value={
    
    this.state.username} placeholder="评论人" onChange={
    
    this.handleForm} />
                    

                    <textarea name="commentContent" value={
    
    this.state.commentContent} cols="30" rows="10" placeholder="评论内容" onChange={
    
    this.handleForm}></textarea>
                    

                    <button onClick={
    
    this.pubComment}>发表评论</button>
                </div>
                {
    
    
                    this.renderList()
                }
            </div>
        )
    }
}

// 渲染
ReactDOM.render(<App />, document.getElementById('root'))

前端学习交流QQ群,群内学习讨论的氛围很好,大佬云集,期待你的加入:862748629点我加入

猜你喜欢

转载自blog.csdn.net/weixin_43974265/article/details/121713162
今日推荐