React学习笔记 - 组件的基本概念

526 阅读4分钟

一、React 元素和组件

1、元素 Element

const div = React.createElement("div", null, "xxx") // 等价于如下JSX
const div = <div>xxx</div>

React 元素,通常用小写。

2、组件 Component

const Div = ()=>React.createElement("div", null, "xxx") //等价于如下JSX
const Div = ()=><div>xxx</div>

React 组件,通常用大写,它是一个返回 React 元素的函数。

React 中有两种组件:

  • 函数组件
  • 类组件

2-1、函数组件

【示例】

function Welcome(props){
    return <h1>Hello, {props.word}</h1>
}

使用方法:

<Welcome word="react" />word 会被传入函数组件作为 props 的属性,即 props.word === "react"

2-2、类组件

【示例】

class Welcome extends React.Component{
    render(){
        return <h1>Hello, {this.props.word}</h1>
    }
}

使用方法:

<Welcome word="react" />

2-3、组件的使用

  • 原生 HTML 标签,如 <div />,会被转译为 React.createElement("div"),即创建一个 div 元素(虚拟 DOM)

  • 对于一个组件 Welcome,也可以标签的形式使用,写作 <Welcome />,相当于 React.createElement(Welcome),这里有两种处理方式:

    • Welcome 为函数组件,则调用函数,获取其返回值(即 Welcome 组件对应的 React 元素)
    • Welcome 为类组件,则用 new 生成一个该类的实例,然后调用实例的 render 的方法,获取其返回值

二、类组件

1、外部数据 props

组件内用 this.props 接收外部数据

class Son extends React.Component{
    render(){
        return <div>{this.props.n}</div>
    }
}

使用时以组件属性的形式传入:

<Son n="x" /> //传字符串
<Son n={x} /> //传表达式

2、内部数据 state

2-1、初始化

constructor 中初始化 state

class Son extends React.Component{
    constructor(){
        super()
        this.state = {
            n:1
        }
    }
}

2-2、修改

修改 state 时,要用 this.setState 方法,不可直接修改,因为 React 并没有像 Vue 一样监听了 state

2-2-1、setState 的两种用法

setState 传参有两种方式:对象、函数

class Son extends React.Component{
    // ...
    add(){
        this.state.n += 1  //错误写法,直接修改不会更新视图
        this.setState(this.state) //不推荐,不要改旧的state
        this.setState( {n: this.state.n + 1} ) //正确写法,传入一个全新的对象
        this.setState( state => {return {n: state.n + 1}} ) //更高级的写法,传一个函数
    }
}

函数的写法更好,因为 setState异步的,用对象的写法容易造成误解:

add(){
	this.setState({n: this.state.n + 1})
    console.log(this.state.n) //此处本意是希望打出新的n,但实际会打出旧的n
}

用函数的写法可以避免混淆:

add(){
    this.setState(state => {
        const n = state.n + 1
        console.log(n) //此处会打印出新的n
        return {n}
    })
}

setState 会调用传入的函数 fn ,并且传入旧的 state ,即 fn(this.state)

【总结】

  1. setState 异步更新 UI
  2. 不要改动旧的 state,用 setState(/* 全新的对象 */)
  3. 更推荐的写法:setState(/* 函数 */)

2-2-2、setState 对复杂 state 的处理

如果 state 中有多个属性,例如:

state = {
	m:0,
	n:0
}

要修改 n,直接 setState( {n: 1} ) 即可,无需带上 msetState 会自动合并 m,相当于 setState({...state, n: 1})

【注】

只有 state 的第一层属性会自动合并,深层的并不会合并,如:

state = {
    m:0,
    obj: {
        a:0,
        b:0
    }
}

setState( { obj:{a:1} } ),则 b 会被置空。

正确写法:setState( { obj:{...state.obj, a: 1} } )

3、事件绑定

类组件的事件绑定比较复杂,涉及到关于 this 的细节。

假设类组件有 addN 方法:

class Son extends React.Component{
    // ...
    addN(){
        this.setState({n: this.state.n + 1})
    }
}

监听点击事件:

<button onClick={this.addN}>n+1</button>
//此为错误写法!这样会使 this.addN 中的 this 为 undefined
//调用形式为 button.onclick.call(undefined, event)

<button onClick={()=>this.addN}>n+1</button>
//正确写法,使用箭头函数

<button onClick={this.addN.bind(this)}>n+1</button>
//正确写法,将 addN 函数的 this 绑定为当前实例,但麻烦!

由于必须使用箭头函数,而类中的方法(原型)不能写成箭头函数的形式,因此可以将事件监听器作为实例的属性,这是最佳写法:

class Son extends React.Component{
    addN = ()=>this.setState(/* ... */) //等价于以下写法,是一种语法糖
	constructor(){
        this.addN = ()=>this.setState(/* ... */)
    }
}

绑定事件时可直接用 this.addN

<button onClick={this.addN}>n+1</button>

三、函数组件

1、外部数据 props

组件用 props 参数接收外部数据:

function Son(props){
    return <div>{props.n}</div>
}

2、内部数据 state

2-1、初始化

在函数内初始化数据:

function Son(){
    const [n, setN] = React.useState(0) //初始化一个变量n=0,setN为修改n的函数
    return (
        <div>
        	{n}
            <button onClick="{()=>setN(n+1)}">+1</button>
        </div>
    )
}

2-2、修改

单一数据,用 setN 修改 n,传入新值即可。

复杂 state

  • 推荐单独声明各个变量,例如:
const [x, setX] = React.useState(0)
const [y, setY] = React.useState(0)

修改谁,就调用相应的 set 函数即可。

  • 如果使用对象,则比较麻烦:
const [state, setState] = React.useState({m:0, n:0})

要改 n,若只写 setState( {n:1} ),并不会自动合并 m, 会导致 state.m 不存在 。

所以必须带上其余的属性,即setState({...state, n:1})