React_核心概念

1,036 阅读9分钟

一、JSX

1. 什么是JSX

  • Facebook起草的JS扩展语法
  • 本质是一个JS对象,会被babel编译,最终会被转换为React.createElement
  • 每个JSX表达式,有且仅有一个根节点
    • React.Fragment
      const h1 = (
          <h1>Hello World <span>span元素</span></h1>
      )
      // 编译成:
      React.createElement("h1", {}, "Hello World", React.createElement("span",
          {},
          "span元素"
      ))
      
      // 如果有的时候最外层必须有两个节点,又不想套一个真实的元素,就可以:
      <>
          <h1>Hello World <span>span元素</span></h1>
          <p>一个p元素</p>
      </>
      // <></>是一个语法糖,就相当于写为:
      <React.Fragment>
          <h1>Hello World <span>span元素</span></h1>
          <p>一个p元素</p>
      </React.Fragment>
      
  • 每个JSX元素必须要有结束(XML规范)。自结束也可以

2. 在JSX中嵌入表达式

表达式要写在大括号中

  • 可以将表达式作为内容的一部分
    • null、undefined、false不会显示
    • 普通对象,不可以作为子元素。会报错
    • 可以放置React元素对象
    • 如果放置数组,会遍历数组,将每一项作为子元素
  • 可以将表达式作为元素属性
    • 在JSX中属性class要写为className
const url = ".......";
const div = (
    <div>
        <img src={url} alt=""/>
    </div>
)
// style写这样写:
<img style={{
    // 外面的{}说明里面写的是表达式
    // 里面的{}是一个对象
    marginLeft: "50px",
}}/>
  • 属性使用小驼峰命名法

  • 防止注入攻击

    • 自动编码
    const content = "<h1>abcabc</h1><p>123123</p>"
    const div = (
     <div>
         {content}
     </div>
    )
    // 页面上显示的是:<h1>abcabc</h1><p>123123</p>
    // 相当于调用的是div.innerText方法
    
    • dangerouslySetInnerHTML
    // 如果有的时候我们知道内容是安全的,可以作为元素放进去
    const content = "<h1>abcabc</h1><p>123123</p>"
    const div = (
      <div dangerouslySetInnerHTML={{
          __html: content
      }}>
      </div>
    )
    // 如果你这么写还没被恶心到的话,说明你真的有这种需求
    
  • 在JSX中使用注释,用的是js注释

3. react元素的不可变性

  • 虽然JSX元素是一个对象,但是该对象中的所有属性不可更改
  • 如果确实需要更改元素的属性,需要重新创建JSX元素

实际上是调用Object.freeze()方法把对象冻结了

二、组件和组件属性

组件:可以理解为包含内容、样式和功能的UI单元

1. 创建一个组件

特别注意:组件的名称首字母必须大写,否则会被解析为一个普通元素

  1. 函数组件

返回一个React元素

  1. 类组件

必须继承React.Component

必须提供render函数,该方法必须返回react元素,用于渲染组件

2. 组件的属性

  1. 对于函数组件,属性会作为一个对象的属性,传递给函数的参数
  2. 对于类组件,属性会作为一个对象的属性,传递给构造函数的参数

组件的属性,应该使用小驼峰命名法

组件无法改变自身的属性

之前学习的React元素(JSX),本质上,就是一个组件(内置组件)。

可以将一个组件和一个用JSX语法写的元素打印输出看一下,是一样的。所以,JSX元素的属性和组件属性都不可更改

React中的哲学:数据属于谁,谁才有权力改动

所以,React中的数据,是自顶而下流动的

三、组件状态

组件状态:组件可以自行维护的数据

组件状态仅在类组件中有效

状态(state),本质上是类组件的一个属性,是一个对象

状态初始化

状态是必须要初始化的

  1. 在构造函数中初始化
constructor(props) {
    super(props);
    this.state = {
        left: this.props.number
    }
}
  1. 在类中直接写
state = {
    left: this.props.number
}

状态的变化

组件中的状态不能直接改变:因为React无法监控到状态发生了变化

必须使用this.setState({})改变状态

一旦调用了this.setState,会导致当前组件重新渲染,传入对象,与之前的状态进行混合

组件中的数据

  1. props:该数据是由组件的使用者传递的数据,所有权不属于组件自身,因此组件无法改变该数据
  2. state:该数据是由组件自身创建的,所有权属于组件自身,因此组件有权改变该数据

四、事件

在React中,组件的事件,本质上就是一个属性。只不过这个属性传递的是一个函数

按照之前React对组件的约定,由于事件本质上是一个属性,因此也需要使用小驼峰命名法

如果没有特殊处理,在事件处理函数中,this指向undefined

因为当父组件给一个子组件传入事件函数时,会把这个事件放到子组件的props当中。我们调用的时候肯定是通过props.事件调用的,所以这里函数里的this应该指向props。显然这不是我们想要的,所以,如果没经过特殊处理,react会将组件props中的事件函数中的this指向为undefined。

所以,这里我们可以使用bind和箭头函数

  1. 使用bind函数,绑定this

这样就算在子组件中通过props调用函数,但是因为函数通过bind绑定了this,所以this依然指向父组件对象

// 第一种写法:
constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
}
handleClick() {
    console.log(this);
}

// 第二种写法:传递的时候再绑定
<ChildComp onClick={this.handleClick.bind(this)}/>

  1. 使用箭头函数
handleClick = () => {
    console.log(this);
}

五、深入认识setState

setState,它对状态的改变,可能是异步的

如果改变状态的代码处于某个HTML元素的事件中,则其是异步的,否则是同步

如果遇到某个事件中,需要同步调用多次,需要使用函数的方式得到最新状态

最佳实践:

  1. 把所有的setState当作是异步的
  2. 永远不要信任setState调用之后的状态
  3. 如果要使用改变之后的状态,需要使用回调函数(setState的第二个参数)
  4. 如果新的状态要根据之前的状态进行运算,使用函数的方式改变状态(setState的第一个函数)

React会对异步的setState进行优化,将多次setState进行合并(将多次状态改变完成后,再统一对state进行改变,然后触发render)

this.setState({
    
}, () => {
    // 回调函数,是状态更新完,并且重新渲染后才执行
})
handleClick = () => {
    this.setState({
        n: this.state.n + 1
    })
    this.setState({
        n: this.state.n + 1
    })
    this.setState({
        n: this.state.n + 1
    })
}
// 这样的话,每次点击按钮执行函数,n都是+1,而不是+3.因为setState这里是异步的,
// 每次读取的时候都是原来的值

// 如果真的遇到这种情况怎么办呢?
// setState的第一个参数也可以写成函数,就可以解决上述问题
handleClick = () => {
    this.setState(cur => {
    // 参数cur表示当前的状态
    // 该函数的返回结果,会混合(覆盖)掉之前的状态
    // 该函数也是异步执行的
    // 但是,cur这个参数是可信任的
        return {
            n: cur + 1
        }
    })
    this.setState(cur => {
        return {
            n: cur + 1
        }
    })
    this.setState(cur => {
        return {
            n: cur + 1
        }
    })
}

六、生命周期

生命周期:组件从诞生到销毁会经历一系列的过程,该过程就叫做生命周期。React在组件的生命周期中提供了一系列的钩子函数(类似于事件),可以让开发者在函数中注入代码,这些代码会在适当的时候运行。

生命周期仅存在于类组件中,函数组件每次调用都是重新运行函数,旧的组件即刻被销毁

1. 旧版生命周期

React版本 < 16.0.0

  1. constructor(构造函数)
    1. 同一个组件对象只会创建一次(所以构造函数指挥运行一次。除非组件被销毁)
    2. 不能在第一次挂载到页面之前,调用setState,为了避免问题,构造函数中严禁使用setState
  2. componentWillMount(组件即将被挂载到页面)
    1. 正常情况下,和构造函数一样,它只会运行一次
    2. 可以使用setState,但是为了避免bug,不允许使用,因为在某些特殊情况下,该函数可能被调用多次
  3. render(渲染虚拟DOM,成为真实DOM)
    1. 返回一个虚拟DOM,会被挂载到虚拟DOM树中,最终渲染到页面的真实DOM中
    2. render可能不只运行一次,只要需要重新渲染,就会重新运行
    3. render函数里严禁使用setState,因为可能会导致无限递归渲染
  4. componentDidMount(虚拟DOM成为真实DOM后)
    1. 只会执行一次
    2. 可以使用setState
    3. 通常情况下,会将网络请求、启动计时器等一开始需要的操作,书写到该函数中
  5. 组件进入活跃状态(等待需要重新渲染的时候(属性或状态发生变化))
  6. componentWillReceiveProps
    1. 即将接收新的属性值
    2. 参数为新的属性对象
    3. 该函数可能会导致一些bug,所以不推荐使用
  7. shouldComponentUpdate
    1. 指示React是否要重新渲染该组件,通过返回true和false来指定
    2. 默认情况下,会直接返回true
  8. componentWillUpdate
    1. 组件即将被重新渲染
  9. componentDidUpdate
    1. 往往在该函数中使用dom操作,改变元素
  10. componentWillUnmount
    1. 通常在该函数中销毁一些组件依赖的资源,比如计时器

2. 新版生命周期

React >= 16.0.0

React官方认为,某个数据的来源必须是单一的

  1. getDerivedStateFromProps
    1. 通过参数可以获取新的属性和状态
    2. 该函数是静态的
    3. 该函数的返回值会覆盖掉组件状态
    4. 该函数几乎是没有什么用
  2. getSnapshotBeforeUpdate
    1. 真实的DOM构建完成,但还未实际渲染到页面中。
    2. 在该函数中,通常用于实现一些附加的dom操作
    3. 该函数的返回值,会作为componentDidUpdate的第三个参数

七、传递元素内容

内置组件:div、h1、p等

<div>
asdfafasfafasdfasdf
</div>

如果给自定义组件传递元素内容,则React会将元素内容作为children属性传递过去。

八、表单

先了解一下 受控组件和非受控组件

受控组件:组件的使用者,有能力完全控制该组件的行为和内容。通常情况下,受控组件往往没有自身的状态,其内容完全收到属性的控制。

非受控组件:组件的使用者,没有能力控制该组件的行为和内容,组件的行为和内容完全自行控制。

表单组件,默认情况下是非受控组件,一旦设置了表单组件的value属性,则其变为受控组件(单选和多选框需要设置checked)