React 组件基础

我睡着的时候不困唉大约 10 分钟前端框架React组件基础组件创建事件处理修改状态表单处理

React 组件基础

组件基本介绍

  • 组件是 React 中最基本的内容,使用 React 就是在使用组件
  • 组件表示页面中的部分功能
  • 多个组件可以实现完整的页面功能
  • 组件特点:可复用,独立,可组合

React 创建组件的两种方式

函数组件

提示

函数组件:使用 js 函数或者箭头函数创建的组件

  • 为了区分普通标签,函数组件的名称必须大写开头
  • 函数组件必须有返回值,表示该组件的结构
  • 如果返回值为 null,表示不渲染任何内容

使用函数创建组件

function Hello() {
  return <div>这是我的函数</div>
}

使用箭头函数创建组件

const Hello = () => <div>箭头函数组件</div>

使用组件

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

类与集成

class 基本语法

  • 在 ES6 之前通过构造函数创建对象
  • 在 ES6 中新增了一个关键字 class, 类 和构造函数类似,用于创建对象
    • 类与对象的区别
    • 类:指的是一类的事物,是个概念,比如车 手机 水杯等
    • 对象:一个具体的事物,有具体的特征和行为,比如一个手机,我的手机等, 类可以创建出来对象。
  • 类创建对象的基本语法
    • 基本语法class 类名{}
    • 构造函数constructor的用法,创建对象
    • 在类中提供方法,直接提供即可
    • 在类中不需要使用,分隔

extends 实现继承

  • extends 基本使用
  • 类可以使用它继承的类中所有的成员(属性和方法)
  • 类中可以提供自己的属性和方法
  • 注意:如果想要给类中新增属性,必须先调用 super 方法

类组件

提示

类组件:使用 ES6 的 class 语法创建组件

约定:

  • 类组件的名称必须是大写字母开头
  • 类组件应该集成 React.Component 父类,从而可以使用父类的方法和属性
  • 类组件必须提供render方法
  • render 方法必须有返回值,表示该组件的结构

定义组件

class Hello extends React.component {
  render() {
    return <div>类组件:有命名规范、有继承、有返回</div>
  }
}

使用组件

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

将组件提取到单独的 js 文件中

思考:项目中的组件多了之后,该如何组织这些组件呢?

  • 选择一:将所有组件放在同一个 JS 文件中
  • 选择二:将每个组件放到单独的 JS 文件中
  • 组件作为一个独立的个体,一般都会放到一个单独的 JS 文件中

实现方式

  1. 创建 Hello.js
  2. 创建组件(函数 或 类)
  3. 在 Hello.js 中导出该组件
  4. 在 index.js 中导入 Hello 组件
  5. 渲染组件,
function Hello() {
  return <div>创建组件</div>
}
export default Hello
import Hello from './Hello'
// 渲染组件
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)

有状态组件和无状态组件

  • 函数组件又叫做无状态组件 函数组件是不能自己提供数据
  • 类组件又叫做有状态组件 类组件可以自己提供数据,组件内部的状态(数据如果发生了改变,内容会自动的更新)数据驱动视图
  • 状态(state)即组件的私有数据,当组件的状态发生了改变,页面结构也就发生了改变。
  • 函数组件是没有状态的,只负责页面的展示(静态,不会发生变化)性能比较高
  • 类组件有自己的状态,负责更新 UI,只要类组件的数据发生了改变,UI 就会发生更新。
  • 在复杂的项目中,一般都是由函数组件和类组件共同配合来完成的。【增加了使用者的负担,所以后来有了 hooks】

无状态组件

function notSateComponent() {
  return <div>无状态组件</div>
}

export default notSateComponent

有状态组件

class Hi extends React.Component {
  constructor() {
    super()
    this.state = {
      count: 0
    }
  }

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

  render() {
    return (
      <div>
        <div>有状态组件{this.state.count}</div>
        {/* 
        setState() 修改状态
          作用:
            1.修改 state
            2.更新UI
          语法:this.setState({要修改的数据})
          思想:数据驱动视图
        JSX 内抽离事件处理函数,直接访问函数会出现事件处理函数的this值为 undefined
        通过箭头函数进行绑定事件处理函数,利用的是箭头函数自身不绑定 this的特点
        */}

        <button onClick={() => this.handleClick()}>+1</button>
      </div>
    )
  }
}
export default Hi

组件渲染

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
  <div>
    <notSateComponent />
    <Hi />
  </div>
)

类组件的状态

  • 状态state既数据,是组件内部的私有数据,只有在组件内部可以使用
  • state值是一个对象表示一个组件中可以有多个数字据
  • state 的基本使用
class Hello extends React.Component {
  constructor() {
    super()
    // 组件通过state提供数据
    this.state = {
      msg: 'hello react'
    }
  }
  render() {
    return <div>state中的数据--{this.state.msg}</div>
  }
}
  • 简洁的语法
class Hello extends React.Component {
  state = {
    msg: 'hello react'
  }
  render() {
    return <div>state中的数据--{this.state.msg}</div>
  }
}

react 调试插件安装

谷歌插件react-devtools

事件处理

注册事件

React 注册事件与 DOM 的事件语法非常像

  • 语法:on+事件名={事件处理}比如onClick={this.handleClick}
  • 注意:React 事件采用驼峰命名法,比如onClickonChangeonMouseEnter
class App extends React.Component {
  render() {
    return (
      <div>
        <button onClick={this.handleClick}>点击事件</button>
      </div>
    )
  }

  handleClick() {
    console.log('点击事件触发')
  }
}

事件对象

可以通过事件处理程序的参数获取到事件对象

function handleClick(e) {
  e.preventDefault()
  console.log(e, '事件对象')
}

;<a onClick={this.handleClick}>点击,不会跳转页面</a>

this 指向问题

  • 事件处理程序中的 this 指向是 undefined
  • render 方法中的 this 指向是当前 react 组件,只有事件畜栏里程序中的 this 有问题
class App extends React.Component {
  state = {
    msg: 'hello react'
  }
  handleClick() {
    console.log(this.state.msg)
  }
  render() {
    return (
      <div>
        <button onClick={this.handleClick}>点我</button>
      </div>
    )
  }
}

this 指向问题解决方案

  1. 方案 1:箭头函数
  2. 方案 2:bind 修改 this 指向
  3. 方案 3:类实现方法

在 render 中使用箭头函数

箭头函数的特点:自身没有 this,访问的是外部的 this

方式 1:

class App extends React.Component {
  state = {
    msg: '文本信息'
  }
  render() {
    return (
      <div>
        <button
          onClick={() => {
            console.log(this.state.msg)
          }}
        >
          点击
        </button>
      </div>
    )
  }
}

缺点:会把大量的 js 处理逻辑放到 JSX 中,不便于维护和阅读

方式 2:

class App extends React.Component {
  state = {
    msg: '提示文本'
  }
  handleClick() {
    console.log(this.state.msg)
  }
  render() {
    return (
      <div>
        <button
          onClick={() => {
            this.handleClick()
          }}
        >
          点击
        </button>
      </div>
    )
  }
}

缺点:把大量的 js 逻辑写在了 JSX 结构中,不好维护

使用 bind

class App extends React.Component {
  state = {
    msg: '消息提示'
  }
  handleClick() {
    console.log(this.state.msg)
  }
  render() {
    return (
      <div>
        <button onClick={this.handleClick.bind(this)}></button>
      </div>
    )
  }
}

或者

class App extends React.Component {
  constructor() {
    super()
    this.handleClick = this.handleCkick.bind(this)
  }
  state = {
    msg: '消息提示'
  }
  handleClick() {
    console.log(this.state.msg)
  }
  render() {
    return (
      <div>
        <button onClick={this.handleClick}>点击</button>
      </div>
    )
  }
}

class 实例方法

class App extends React.Component {
  state = {
    msg: '消息提示'
  }

  handleClick = () => {
    console.log(this.state.msg)
  }

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

注意:这个语法是试验性的语法,但是有 babel 的转义,所以没有任何问题

setState 修改状态

  • 组件中的状态是可变的
  • 语法:this.setState({要修改的数据})
  • 注意:不要直接修改 state 中的值,必须通过this.setState() 方法进行修改
  • setState 的作用:修改 state 值,更新 UI
class App extends React.Component {
  state = {
    count: 1
  }
  handleClick() {
    this.setState({
      count: this.state.count + 1
    })
  }

  render() {
    return (
      <div>
        <p>点击次数:{this.state.count}</p>
        <button onClick={this.handleClick.bind(this)}>点击+1</button>
      </div>
    )
  }
}
  • react 中核心理念:状态不可变
    • 不要直接修改 react 中 state 的值,而是提供新的值
    • 直接修改 react 中 state 的值,组件并不会更新

表单处理

提示

实际业务场景:操作表单元素,比如:获取表单值,设置表单值

react 中处理表单元素有两种方式:

  • 受控组件
  • 非受控组件(DOM 操作)

受控组件概念

  • HTML 中表单元素都是可输入的,即表达那用户维护自己的可变状态(value)
  • 在 React 中,可变状态通常是保存在 state 中,并要求状态只能通过setState进行修改
  • React 中将 state 中的数据与表达那元素的 value 值绑定到一起,由state的值来控制表单元素的值
  • 受控组件:value 值收到了 react 控制的表单元素

受控组件使用步骤

  1. 在 state 中添加一个状态,作为表单元素的 value 值(控制表单元素的值)
  2. 给表单元素添加 change 事件,设置 state 的值为表单元素的值(控制值的变化)
class App extends React.Component {
  state = {
    msg: '提示消息'
  }

  handleChange = (e) => {
    this.setState({
      msg: e.target.value
    })
  }

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

常见受控组件

文本框、文本域、下拉框(操作 value 属性)、复选框(操作 checked 属性)

class App extends React.Component {
  state = {
    userName: '',
    desc: '',
    city: '2',
    isSingle: true
  }

  handleName = (e) => {
    this.setState({
      userName: e.target.value
    })
  }
  handleDesc = (e) => {
    this.setState({
      desc: e.target.value
    })
  }
  handleCity = (e) => {
    this.setState({
      city: e.target.value
    })
  }
  handleSingle = (e) => {
    this.setState({
      isSingle: e.target.checked
    })
  }

  render() {
    return (
      <div>
        姓名:
        <input
          type="text"
          value={this.state.userName}
          onChange={this.handleName}
        />
        <br />
        描述:
        <textarea value={this.state.desc} onChange={this.handleDesc} />
        <br />
        城市:
        <select value={this.state.city} onChange={this.handleCity}>
          <option value="1">北京</option>
          <option value="2">上海</option>
          <option value="3">广州</option>
          <option value="4">深圳</option>
          <option value="5">郑州</option>
          <option value="6">杭州</option>
        </select>
        <br />
        是否唯一:
        <input
          type="checkbox"
          checked={this.state.isSingle}
          onChange={this.handleSingle}
        />
      </div>
    )
  }
}

多表单元素的优化

  • 问题:每个表单元素都需要一个单独的事件处理程序,处理太繁琐
  • 优化:使用一个事件处理程序处理多个表单元素
  • 步骤:
    • 给表单元素添加 name 属性,名称与 state 属性名相同
    • 根据表单元素类型获取对应值
    • 在事件处理程序中通过[name]修改对应的 state
class App extends React.Component {
  state = {
    username: '',
    desc: '',
    city: '2',
    isSingle: true
  }

  handleChange = (e) => {
    let { name, type, value, checked } = e.target
    console.log(name, type, value, checked)
    value = type === 'checkbox' ? checked : value
    console.log(name, value)
    this.setState({
      [name]: value
    })
  }
  render() {
    return (
      <div>
        姓名:
        <input
          type="text"
          name="username"
          value={this.state.username}
          onChange={this.handleChange}
        />
        <br />
        描述:<textarea
          name="desc"
          value={this.state.desc}
          onChange={this.handleChange}
        ></textarea>
        <br />
        城市:
        <select
          name="city"
          value={this.state.city}
          onChange={this.handleChange}
        >
          <option value="1">北京</option>
          <option value="2">上海</option>
          <option value="3">广州</option>
          <option value="4">深圳</option>
        </select>
        <br />
        是否唯一:
        <input
          type="checkbox"
          name="isSingle"
          checked={this.state.isSingle}
          onChange={this.handleChange}
        />
      </div>
    )
  }
}

非受控组件-ref

提示

非受控组件借助于 ref,使用原生 DOM 的方式来获取表单元素的值,类似于 Vue 通过 ref 绑定组件获取 DOM 元素

使用步骤:

  1. 调用React.createRef()方法创建一个 ref
constructor() {
  super()
  this.txtRef = React.createRef()
}
txtRef = React.createRef()
  1. 将创建好的 ref 对象添加到文本框中
<input type="text" ref={this.txtRef} />
  1. 通过 ref 对象获取文本框的值
handleClick = () => {
  console.log(this.txtRef.current.value)
}

非受控组件用的不多,推荐使用受控组件

评论案例

class CommentList extends React.Component {
  state = {
    commentList: [
      { id: 1, name: '张三', content: '混合开发岗位' },
      { id: 2, name: '李四', content: 'webGL开发岗位' },
      { id: 3, name: '王五', content: '研发岗位' }
    ],
    userName: '',
    userContent: ''
  }

  // 渲染列表
  renderList() {
    let { commentList } = this.state
    if (commentList.length === 0) return <div>暂无数据~</div>
    return (
      <div>
        <ul>
          {commentList.map((item) => (
            <li key={item.id}>
              <h3>评论人:{item.name}</h3>
              <p>评论内容:{item.content}</p>
            </li>
          ))}
        </ul>
      </div>
    )
  }

  // 处理表单元素值
  handleForm = (e) => {
    const { name, value } = e.target
    this.setState({
      [name]: value
    })
  }

  // 发表评论:
  addComment = () => {
    // 解构 state
    const { commentList, userName, userContent } = this.state

    // 将评论信息添加到state中
    const newComments = [
      {
        id: Math.random(),
        name: userName,
        content: userContent
      },
      ...commentList
    ]
    this.setState({
      commentList: newComments
    })
  }
  // 清空评论
  clearComment = () => {
    this.setState({
      commentList: []
    })
  }

  render() {
    const { userName, userContent } = this.state

    return (
      <div>
        <div>
          <input
            className="user"
            type="text"
            placeholder="请输入评论人"
            value={userName}
            name="userName"
            onChange={this.handleForm}
          />
          <br />
          <textarea
            className="content"
            cols="30"
            rows="10"
            placeholder="请输入评论内容"
            value={userContent}
            name="userContent"
            onChange={this.handleForm}
          />
          <br />
          <button onClick={this.addComment}>发表评论</button>
          <button onClick={this.clearComment}>清空评论</button>
        </div>
        {/* 通过条件渲染内容 */}
        {this.renderList()}
      </div>
    )
  }
}

export default CommentList