学习 React.js 需要了解的一些概念

970 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

大家好,我是前端西瓜哥。

React.js 是被广泛使用的用于构建用户界面的 JS 库。下面给大家介绍一下学习 React 需要了解的一些概念。

声明式

在 React 这类框架流行之前,我们更新 UI,都是命令式的:我们需要手动指定元素,然后命令它改变样式。

// 纯 JS 写法
document.querySelector('#content') = '前端西瓜哥';

// JQuery 写法
$('#content').text('content');

这种写法其实是相当繁琐的。你可能需要给元素加上 id,加上 class 作以区分,可能还需通过查找当前元素的父元素来界定区域,然后手动作添加子元素、进行修改样式、移除等操作。

所有的事情,都要自己一个个去操作,事无巨细,容易写错。

React 和 Vue 这类框架的出现,让实际项目开发中命令式的写法成为了历史(只是偶尔还是会用到一点命令式写法)。

React 的编码是 声明式 的,你只需要维护好状态,然后在必要的时候通知 React,React 会自动帮你将 UI 渲染好。

class Compoent extends React.Component {
  state = { content'前端西瓜哥' };

  updateContent = () => {
    this.setState({ content'fe_watermelon' });
  };

  render() {
    return (
      <div>
        <div>{this.state.content}</div>
        <button onClick={() => this.updateContent()}>更新 content</button>
      </div>
    );
  }
}

// 渲染为
// <div>前端西瓜哥</div>

React 中用 state 对象来保存可能会发生变化的状态,然后在 HTML 中用 {变量} 的方式嵌入,并通过 setState() 方法来更新状态,并重新渲染 UI。

UI = f(data)

数据映射到 UI 的操作过程,全部交给 React 做了,再也不用担心自己手写的 DOM 更新写错了。你要做的只是更新状态,以及设置好状态在 HTML 中渲染的地方。

这种修改好状态,然后调用方法更新 UI 的做法,其实挺像做小游戏的。比如我们要做一个俄罗斯方块,当发生动作时,我们要更新好每个像素的位置(更新数据),然后调用自己实现的 render 方法,先清空画布,然后一个个绘制出来(映射 UI)。

组件化

组件化是什么?其实就是将模板、样式、脚本组合起来,成为一个组件。我们编写好一个个小的组件,然后将组件组合起来,就组成了我们的 UI 界面。

要想构建复杂的应用,合适的抽象是很重要的。对于 UI 来说,一种比较正确的思路,是将 UI 的分割成各个部分,我们只需要专注于一个职责专一的组件上,完成它后,再和其他的组件联合在一起,一点点构建出复杂的应用。

比如一个待办功能,我们可以抽象成下面这个样子。

<TodoContainer>
  <TodoInput />
  <TodoList />
</TodoContainer>

虚拟 DOM

为了高效地更新 DOM,React 的底层使用了虚拟 DOM 来表示真实的 DOM 结构。

没有使用真实 DOM,是因为真实 DOM 对象比较复杂,有非常多的属性,比较占内存,所以抽象了一层 虚拟 DOM,此外还能附带上组件节点。

当状态更新时,会对比新旧虚拟 DOM 树得到补丁,通过打补丁的方式去更新真实 DOM 树。

React 中新旧 DOM 对比算法做了优化,要比普通的树比较算法要高效,这是 React 能被运用到实际生产环境的一个重要原因。

跨平台

得益于 React 中虚拟 DOM 的实现,React 支持通过 React Native 编译为其他平台语言,来构建移动原生应用,比如 Andorid 或 iOS 原生应用。

虚拟 DOM 其实是真正 DOM 的一层抽象,这个抽象和平台无关,因此我们可以根据这层抽象去做跨平台的实现,让一套代码得以运行在不同平台。

但虚拟 DOM 并不能涵盖不同平台一些特有的 API,所以你其实还是要懂一些原生 APP 的知识的,对一些情况做特殊处理。

但至少,React 提供了一种跨平台的方案。

JSX

React 首先是一个 JS 库,它的语法不能跳脱 JS 的语法。在 JS 环境中,React 通过 React.crreatElement 来描述 DOM。

创建一个内容为  this.state.content 的 div 元素,我们需要这样写。

const el = React.createElement('div', {}, this.state.content);

第一个是 HTML 元素名或组件名,第二个是属性,第三个是子元素或子组件。

只是描述的一个 DOM 元素还算简单,但我们的页面是由复杂的 DOM 元素组织而成的,如果用上面这种写法,代码容易出错,也不方便进行维护。

于是 React 推出了 JSX,全称为 JavaScript Syntax Extension,即 JS 语法的扩展增强。通过 JSX,我们就可以实现 “在 JS 中写 HTML”。

const el = <div>{this.state.content}</div>;

但 JSX 还是不能被浏览器识别,需要编译为 JS。编译后,里面的这种 <div></div> 就会转换为使用了 React.createElement('div') 的代码。

单向数据流

React 要求数据从父组件通过 props 流向子组件,这样就能更好地定位组件中的某个数据的来源。

当然我们还是可以通过额外传递一个可以修改父组件状态方法给子组件,通过它去更新传给子组件的数据。

这样就能实现双向的数据流,但它依旧是显式的,仍然有助于理解程序的运转流程。

React Hooks

React 支持通过类或函数的方式编写组件,分别称为 类组件函数组件

类组件可以创建对象,所以自身可以维护状态是理所当然的事情。

一般来说,函数组件是不支持状态的,因为它是一个函数,只会接收外部传入的数据,然后渲染,内部没办法保持状态,因为它不是对象,只是一个用完就结束的函数。

函数组件不能使用内部状态的这种情况,在 React 推出了 React Hooks 之后解决了。

const Compoent = () => {
  const [content, setContent] = useState('前端西瓜哥');

  return (
    <div>
      <div>{content}</div>
      <button onClick={() => setContent('fe_watermelon')}>更新</button>
    </div>
  );
};

我们在函数组件内使用 useState() 就可以创建一个内部状态,且可以通过 setXxx() 进行更新。

我第一次看到 React Hooks,就觉得这简直就是魔法,惊为天人。

底层其实是用一个额外的有序列表保存好了每个状态,和函数组件进行了绑定。为了做到这点,React 要求 Hooks 必须在每次执行时,保证顺序相同。

React Hooks 的一个优点是,将逻辑的最小单元从组件,缩小为更细粒度的 hooks 的组合。这让我们编写组件更加灵活。

首发于我的公众号:前端西瓜哥