React核心概念

143 阅读7分钟

React核心概念

JSX

JSX是一个JavaScript的语法扩展,在jsx语法中,可以在大括号内放置任何有效的JavaScript表达式。

  • ReactDom会在渲染所有输入内容之前,默认进行转义,所有的内容在渲染之前都被转换成了字符串。所以JSX可以防止XSS(注入攻击)
  • Babel会把JSX转译为一个名为React.createElement()函数调用。比如:
const element = (
    <h1 className="greeting">
        hello, world!
    </h1>
)

等价于:

const element = React.createElement(
    "h1",
    {
        className: "greeting"
    },
    "hello, world!"
)

函数组件和class组件

class组件

生命周期方法

class组件中具有生命周期方法,常见的生命周期方法有:construcor, render, componentDidMount, componentDidUpdate, componentWillUnmount。

state状态管理

class组件中使用state来进行内部状态管理,使用setState要注意:

  1. 必须要使用setState来修改state状态

  2. state的更新可能是异步的,也可能是同步的。如果要使用上一次的状态来计算新的状态,可以让setState接受一个函数而不是一个对象。

this.setState((state,props) => {
    counter: state.counter + props.increment
})
  1. state的更新可能会被合并

函数组件

React 16.8版本支持了React hooks,函数组件内部也可以维护管理状态。

事件处理

React中元素的事件处理与Dom的事件处理相似,不同之处在于:

  1. React事件的命名采用驼峰命名,而不是纯小写
  2. 使用JSX语法时需要传入一个函数,而不是一个字符串
  3. 不能通过return false的方式阻止默认行为,必须要显式使用preventDefault
  4. 在为事件处理函数传递额外的参数时,事件对象e会被作为第二个参数传递

表单、受控组件与非受控组件

在HTML中,表单元素通常自己维护state,并根据用户输入进行更新。

在React中,可变状态(mutable state)通常保存在组件的state属性中,并且只能通过使用setState来更新。

把二者结合,React的state成为唯一的数据源,而渲染表单的React组件还控制着用户输入过程中表单发生的操作。被React以这种方式控制取值的表单输入元素就叫做受控组件

常见的表单元素(受控组件)

  1. input
<form onSubmit={this.handleSubmit}>
    <label>
        名字:
        <input type="text" value={this.state.value} onChange={this.handleChange}/>
    </label>
    <input type="submit" value="提交" />
</form>
  1. textarea
<form onSubmit={this.handleSubmit}>
    <label>
          文章:
          <textarea value={this.state.value} onChange={this.handleChange} />
    </label>
    <input type="submit" value="提交" />
</form>
  1. select
<form onSubmit={this.handleSubmit}>
    <label>
        选择你喜欢的风味:
        <select value={this.state.value} onChange={this.handleChange}>         
            <option value="grapefruit">葡萄柚</option>
            <option value="lime">酸橙</option>
            <option value="coconut">椰子</option>
            <option value="mango">芒果</option>
        </select>
    </label>
    <input type="submit" value="提交"/>
</form>
  • 当具有多个输入时,我们可以给每个input元素添加name属性,并让处理函数根据event.target.name的值选择要执行的操作。
class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.name === 'isGoing' ? target.checked : target.value;
    const name = target.name;
    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          参与:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          来宾人数:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

非受控组件

使用非受控组件来处理表单数据时,表单数据将交由DOM节点来处理。

要编写一个非受控组件,可以使用ref来从DOM节点中获取表单数据。

在非受控组件中,经常希望赋予组件一个初始值,但是不去控制该值的后续更新,可以指定一个defaultValue属性,而不是value。在一个组件已经挂载之后去更新defaultValue的值,不会造成DOM上值的任何更新。

  • <input type="text"/>, <input type="select"/>,<input type="textarea"/>支持defaultValue
  • <input type="checkbox"/>,<input type="radio"/>支持defaultChecked
  1. 使用非受控组件来接收一个input输入的值
class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.input = React.createRef();
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.input.current.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input defaultValue="Bob" type="text" ref={this.input} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}
  1. 文件输入

在React中,文件输入(即<input type="file"/>)始终是一个非受控组件。因为它的值只能由由用户设置,而不能通过代码控制。应该使用File Api与文件进行交互。

class FileInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.fileInput = React.createRef();
  }
  handleSubmit(event) {
    event.preventDefault();
    alert(
      `Selected file - ${this.fileInput.current.files[0].name}`
    );
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Upload file:
          <input type="file" ref={this.fileInput} />
        </label>
        <br />
        <button type="submit">Submit</button>
      </form>
    );
  }
}

状态提升

在React中,将多个组件中需要共享的state向上移动到它们的最近共同父组件中,便可以实现共享state,即所谓的状态提升.

组件复用

React推荐使用组合而非继承来实现组件间的代码复用。

包含关系

Sidebar,Dialog等展现通用容器的组件中,往往无法提前知晓它们子组件的具体内容。可以使用props.children来将它们的子组件传递到渲染结果中。

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}


function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

还可以通过传入其它的props属性来复用组件,比如预留位置:

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      }
    />
  );
}

特例关系

有些时候,我们会把某些组件看成是其它组件的特殊实例,如:WelcomeDialog 可以说是 Dialog 的特殊实例,也可以用组合来实现。

function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
    </FancyBorder>
  );
}

function WelcomeDialog() {
  return (
    <Dialog
      title="Welcome"
      message="Thank you for visiting our spacecraft!"
    />
  );
}

思考:我们应该如何构建一个应用?

这部分内容在React官网中被命名为React哲学,我个人认为是React中最为重要的部分,因为它是一种对于思考的引导,引导你具有设计组件或应用的能力。

1. 对照设计稿,将设计好的UI划分为组件层次

根据单一功能原则来判定组件的范围,即一个组件原则上只能负责一个功能,如果需要负责多个功能,就应该考虑将它拆分为更小的组件。

实践中,UI结构一般与数据模型一一对应。

划分好 UI组件层次后,我们一般可以得到如下的层级结构:

  • FilterableProductTable

    • SearchBar

    • ProductTable

      • ProductCategoryRow
      • ProductRow

2. 用React创建一个静态版本

在划分好组件层级之后,我们就可以开始编写对应的应用了。React建议先编写一个不包含交互功能的UI,最好将渲染UI和添加交互这两个过程分开。

可以选择自上而下自下而上的方式来构建应用。

  • 对于比较简单的应用,使用自上而下的方式更方便。

  • 对于较为大型的应用,推荐自下而上的方式,并在编写低层级组件的同时为其编写测试。

3. 确定UI state的最小(且完整)表示

编写好静态版本之后,我们要添加交互功能了,想要是UI具备交互功能,需要具有触发基础数据模型改变的能力,React通过实现state来改变基础数据模型。

判断一个数据是否应该属于state:

  • 该数据是否由父组件通过props传递来的?

  • 是否随着时间的推移而保持不变?

  • 能否通过其它state或props计算出该数据的值?

如果一个数据符合上面三项中的任意一项,则这个数据不应该是state。

4. 确定state放置的位置

我们现在已经知道有哪些state了。接下来我们要做的就是把state放置在正确的位置。这一部分其实对应的是状态提升

我们判断一个state应该放在哪里,可以通过这几个步骤:

  1. 找到根据这个state进行渲染的所有组件

  2. 找到它们的共同所有者

  3. 该共同所有者组件或比它层次更高的组件应该拥有该state

  4. 如果找不到合适的位置来存放该state,可以直接创建一个新的组件来存放该state,并将这一新组件置于高于共同所有者组件层级的位置。

在我们的实际开发中,一般不会存在两级以上的组件共用状态,如果出现了多个不同层级的组件共用同一个状态的情况,我们可以使用ReduxProvider等方式来在最上层存储共用状态。

5. 添加反向数据流

反向数据流其实就是:让低层级组件能够更新高层级组件中的state。

我们一般通过父组件给子组件传递一个回调函数,子组件调用该回调函数的方式来实现子组件对于父组件state的更新

结语

以上就是React的所有核心内容了。当然,React能够作为一个主流的前端框架,其内涵不可能仅仅只有这些,还有诸如高阶组件协调hooks等较为复杂的内容,以及ReduxRouter等配套库,我们会在后面的React专栏内容中一一分析。

我是何以庆余年,如果文章对你起到了帮助,希望可以点个赞,谢谢!

如有问题,欢迎在留言区一起讨论。