翻译:疯狂的技术宅
https://www.toptal.com/react/react-tutorial-pt1
前端和 JavaScript 是一个奇怪的世界。大量不断推出的新技术的同时,也在被不需要它们的人嘲笑,往往很多人都会这样做。我们有时会对不断涌现的信息、库和讨论感到不知所措,总希望能有一些稳定的东西,就像能让我们可以休整一段时间的避风港。最近 React 似乎有变成 JavaScript 演变海洋中温暖港湾的趋势。
正是考虑到这一点,我们决定制作这个 React 系列教程,展示它的功能,并看看它与 Angular 和 VueJS 相比有什么特点。
React 是一座耸立在 JavaScript 代码海上醒目的灯塔
当然 React 并不是唯一的选择,但目前它是最受欢迎、最稳定、最具有创新性的解决方案之一,虽然它仍然在不断升级,但更多的是在改进,而不是增加功能。
2019年的 React
React 是一个视图库,可以一直追溯到2011年,当时它的第一个原型名为 FaxJs,并出现在 Facebook 上,React 是由 Jordan Walke(他也是上述原型的作者)于2013年5月29日在 JSConfUS 推出的,并于2013年7月2日在 GitHub 上公开发布。
在2014年,当开始扩大社区并推广 React 时,它受到持续欢迎。然而从我的角度来看,2015年是大型公司(例如 Airbnb 和 Netflix )开始喜欢并采用 React 的里程碑年。此外,当年还出现了React Native。 React Native背后的想法并不是什么全新的东西,不过看起来很有趣,尤其是因为它得到了 Facebook 的支持。
另一个重大变化是 Redux,一个 Flux 实现。这使状态管理方式更加简单友好,使其成为迄今为止最成功的实现。
从其出现一直到现在,还有很多其他的东西供我们使用,包括 React tools【https://www.toptal.com/react/navigating-the-react-ecosystem】,重写了核心算法;Fiber 用于语义转换版本控制等等。到了今天,我们处于 v16.6.3,几周后可能就会发布支持 Hooks 的新版本(它应该在 16.7.0 得到支持,但由于对 React.lazy 做了一些修复,就先发布了一个版本)。React 由于其名气和稳定性获得了广泛好评。
但 React 到底是什么?
好吧,如果你身为前端开发人员但是从来都没有听说过,那么我就要说声“恭喜你”,因为这是一个了不起的壮举。
开个玩笑而已。React 是一个声明式的基于组件的视图库,可以帮助你构建 UI。它是一个库而不是一个框架,虽然最初很多人把它描述为后者。
显然,如果我们要把 Redux 和 React Router 等添加到 React,它就拥有了制作常规单页应用程序所需的所有东西,这可能这就是它有时被错误地描述为框架而不是库的原因 。如果一定要这样认为的话,将该环境的所有组件放在一起,术语“框架”可能有点适合它,但就其本身而言,React 仅仅是一个库。
不要再纠结改怎么对其分类了,先关注 React 有什么独特之处,一些之前没有注意到的东西。首先,当你第一次看到 React 时,就会想到 JSX【https://reactjs.org/docs/jsx-in-depth.html】,因为这是你看到代码时的第一个感受。 JSX是一种 JavaScript 语法扩展,有点类似于 HTML/XML。说到 React 和 JSX,它们与 HTML 有一些区别,例如,React 中的类是 className,没有tabindex 但是有 tabIndex,样式接受具有驼峰命名的属性的 JavaScript 对象,依此类推。
有一些细微的差别,但是任何人都应该立即接受它们。事件处理是通过例如 onChange 和 onClick 属性实现的,这些属性可以用来附加一些函数来处理事件。此外,以后的组件可以通过使用 props 自由重用和自定义,因此没有理由多次编写相同的代码。
1import React, { Component } from 'react';
2
3export default class App extends Component {
4render() {
5return (
6
Hello World, {this.props.name}
7);
8}
9}
但是实际上 JSX 在 React 中并不是非常必要的。你可以编写常规函数来创建元素,而无需使用JSX。上面的代码可以像下面这样去用。
1import React, { Component } from 'react';
2
3export default class App extends Component {
4render() {
5return React.createElement(
6'div',
7null,
8'Hello World, ',
9this.props.name
10);
11}
12}
很显然我并不是建议你用这样的语法,尽管有些情况下它有可能会派上用场(例如你想要引入一个非常小的东西但是又不想更改构建环境)。
实际上我展示上述代码还有另一个原因。通常,开发人员不理解我们为什么需要执行以下操作:
1import React from 'react';
代码片段应该是能够自解释的。即使我们正在提取 Component,仍然需要 React,因为 Babel 在 JSX 之上转换为 React.createElement。所以如果我们不导入 React 就会失效。前面我提到了 Babel,这是一个工具,可以帮助我们预览那些尚未在 JavaScript 中(更确切地说是在浏览器中)支持的东西,或者以某种方式对 JavaScript 进行扩展(或者类似于 TypeScript,Babel 从 Babel 7 开始支持的不同语言)。感谢Babel:
JSX 将被转化为成浏览器可以理解的代码。
我们可以使用尚未在浏览器中实现的新功能(例如类属性)。
我们可以支持新浏览器中的特性,同时在旧浏览器中支持较旧的功能。
简而言之,在 JavaScript 中,就是今天的代码明天仍然能用;这可能需要为此专门再写一篇文章。值得一提的是,React 的导入也可以被一些其他技术绕过(比如通过 Webpack 引入 ProvidePlugin 等),但是由于篇幅有限,我们将避免使用这种方式,并假设用户将使用 Create React App( CRA)(稍后将提到有关此工具的更多信息)。
另一点比 JSX 本身更重要,那就是 React 基于虚拟 DOM。简而言之,虚拟 DOM 是用 JavaScript 编写的在内存中的理想树结构,稍后我们会把它与真实 DOM 进行比较。
怎样与 Angular 和 Vue 进行比较?
我很不喜欢对库进行比较,特别是当我们被迫把梨和苹果放在一起进行比较时。
因此,我将尝试使用一系列简短的问题和答案将 React 与 Angular 和 Vue 进行比较。这种比较与技术相关,而不是主观的作出 “X比Y更好,因为它使用 JSX 而不是模板。” 这种出于个人偏好的对比。另外在速度和内存分配等方面 React 与其主要竞争对手(Angular 和 Vue 都能想得到)非常相似,有一篇关于这个问题的文章很不错,但请记住这一点:绝大多数程序并不会做这种处理上万行数据的事。因此,这些结果也是纯粹的速度实验。实际上你也不会把这放在首位。
React vs. Angular vs. Vue.js
那么让我们来看看关于 React 的问题以及它与竞争对手的比较:
我想拥有更多的工作机会。 React 到底有多受欢迎?
嗯,这很容易回答 —— 选择 React。实际上,我会说 React 的工作机会大约其它的 6 到 10 倍(可能出入比较大,在一些大网站是 50 倍,也有些网站是 6 倍),是 Vue 的 2 到 4倍,比 Angular 更多。对 React experts 【https://www.toptal.com/react】的需求很大,但是为什么 Vue 在 GitHub 上非常受欢迎(实际上它获得了比 React 更多的star)却没有更多的职位空缺?这点我不知道。【译者注:作者是美国人,这里指的是美国的就业市场】
我想要一个很大的社区,还有大量的库,能够快速解决可能出现的问题。
选 React,不要再犹豫了。
它是否容易使用,开发过程是否令人愉快?
2018年和2017年的 JS 状态报告显示,React 和 Vue 都享有良好的声誉,大多数开发人员表示会再次使用。另一方面Angular 有一种趋势,每年都会有越来越多的人说不会再次使用它。
我想创建一个新的单面应用,但我不想额外去找这种支持库。
我认为这大概是 Angular 值得选择的唯一原因。
我不是大公司。但是希望尽可能独立,应该选择哪个?
Vue —— 它是我们三巨头中唯一独立的一个。 ( Facebook 支持 React,而 Google 支持 Angular。)
上手最简单和最快的学习曲线?
Vue/React。在这里我更倾向于 Vue,但这只是我个人的意见。至于为什么?因为你不需要懂 JSX(它是可选的),它基本上只是 HTML + CSS + JavaScript。
React Tutorial:开始你的第一个程序
React tutorial:成功创建 React 应用后的屏幕截图
目前上手 React 最简单方法是使用 CRA,这是一个为你创建项目的 CLI 工具,可帮助你避免配置 Webpack / Babel 等环境。你只需要依赖默的认配置方式,并随着时间的推移更新包含在内的内容。多亏了这一点,你无需关心某些关键库的主要更新。
当然,稍后,你可以通过运行 npm run eject 来“弹出”自己并自己处理每个细节。这种方法有其自身的优点,因为你可以增加原来不可用的东西(例如装饰器)来增强你的应用,但它也可能是令人头疼的问题,因为它需要花费更多的时间去配置许多额外的文件。
所以,首先要做的是:
1npx create-react-app {app-name}
然后 npm run start 就完成了。
类组件与函数组件
我们应该先解释这些组件的不同之处。基本上每个组件可以是 function 或 class。它们之间的主要区别在于,类组件有函数组件中没有的一些功能:它们有 state 并使用 refs、生命周期等。从 v16.7 开始我们可以使用 hooks,因此可以使用 hooks 来进行 state 和 refs。
类组件有两种类型:Component 和 PureComponent。它们唯一的区别是 PureComponent 可以对 props 和 state 进行浅层比较 —— 这在你不想“浪费”渲染资源的情况下有独到的好处,一个组件及其子组件恰好在渲染后处于相同状态。不过它只有一个浅层比较;如果你想实现自己的比较操作(假如你传递的是复杂的 props),只需要用 Component 并覆盖 shouldComponentUpdate(默认情况下返回true)。从 16.6 + 开始,在函数组件中也可以用类似的东西 —— 全靠 React.memo 这个更高阶的组件,在默认情况下表现得像 PureComponent(浅层比较),在你进行自定义的 props 比较时它还需要第二个参数。
一般来说如果你能用函数组件(假设你不需要类功能)那么就用它。不过从 16.7.0 开始,由于生命周期方法,只能用类组件。但是我认为函数组件更透明,更容易推理和理解。
React 生命周期方法
安装、更新和卸载组件
Constructor(props)
可选,CRA 使其变得受欢迎,默认包含 JavaScript 的类字段声明。声明是否通过类中的箭头函数去绑定方法是没有意义的。类似的状态也可以初始化为类属性。
仅用于 ES6 类中初始化对象的本地状态和绑定方法。
componentDidMount()
在这里进行 Ajax 调用。
如果你需要事件监听器,订阅等功能,可以在此处添加。
你可以在这里使用 setState(但是它会使组件重新渲染)。
componentWillUnmount()
清除所有仍在进行的东西 —— 例如,Ajax应该被中断,取消订阅,清除定时器等等。
不要调用 setState,因为它没有意义,因为组件将会被卸载(并且你会得到一个警告)。
componentDidUpdate(prevProps, prevState, snapshot)
在组件刚刚更新完毕时执行(在开始渲染时不会)。
有三个可选的参数(以前的props,以前的 state 和只有在你的组件实现 getSnapshotBeforeUpdate 时才会出现的快照 )。
仅当 shouldComponentUpdate 返回true时才会执行。
If you use setState here, you should guard it or you will land in an infinite loop.
如果你在这里用到了 setState,应该保护它,否则将会陷入无限循环。
shouldComponentUpdate(nextProps, nextState)
仅用于性能优化。
如果返回 false,则不会调用渲染器。
如果重写的 SCO 只是对 props/state的浅层比较,可以使用 PureComponent。
getSnapshotBeforeUpdate()
可用于保存一些与当前 DOM 有关的信息,例如当前的滚动位置,稍后可在 componentDidUpdate 中重用,用来恢复滚动的位置。
componentDidCatch(error, info)
应该发生日志记录错误的地方。
可以调用 setState,但在以后的版本中,将会在静态方法getDerivedStateFromError(error) 中被删除,它将通过返回一个值来更新状态。
还有两种静态方法,在其他的段落中提到过
static getDerivedStateFromError(error)
此处提供错误信息。
应返回一个对象值,该值将会更新可用于处理错误的状态(通过显示内容)。
由于它是静态的,因此无法访问组件实例本身。
static getSnapshotBeforeUpdate(props, state)
应该在 props 随时间变化的情况下使用 —— 例如根据 React docs,它可能用于转换组件。
由于它是静态的,因此无法访问组件实例本身。
注意,目前还有更多可用的方法,但它们可能会在 React 17.0 中被删除,所以就不在这里没有提起了。
State vs. Props
我们先从 Props 开始,因为它更容易解释。Props 是传给组件的属性,以后可以在组件显示信息或业务逻辑时重用它 。
1import React, { Component } from 'react';
2
3export default class App extends Component {
4render() {
5
6return (
7
8
9
10);
11}
12}
13
14const HelloWorld = (props) =>
Hello {props.name}
在上面的例子中,name 是一个 prop。prop 是只读元素,不能直接在子组件中更改。很多人有一种不太好的习惯,那就是把 prop 复制到 state ,然后再对 state 进行操作。当然有时候你希望执行类似 “在提交之后去更新父组件的初始状态” 这样的操作,但这种情况非常少见 —— 在这种情况下,更新初始状态可能有意义。另外不仅可以给子组件传递字符串这样的 prop ,还可以传递数字、对象、函数等。
prop 还有一个更有用的东西叫做 defaultProps,这是一个静态字段,它可以告诉你组件的默认 prop 是什么(比如当它们没有传递给组件时)。
在“状态提升”的情况下,其中一个组件(父组件)具有稍后由其子组件重用的状态(例如,一个子组件用来显示而另一个用来编辑),那么我们需要将该功能从父组件传递给子组件。 它允许我们更新父级的本地状态。
另一方面,状态是一个可以修改的本地状态,但是通过 this.setState 间接修改。如果直接去改变状态,组件将不会感知到,更不会因为状态的改变而重新渲染。
**SetState **是一种更改本地状态对象的方法(通过执行浅层合并),之后组件通过重新渲染自己来响应它。请注意,在使用 setState 之后,this.state 属性不会立即对更改(它具有异步性质)作出反应,因为优化的原因,可能会将 setState 的几个实例一起进行批处理。调用它的方式有好几种,其中一种方式允许我们在对状态进行更新能够后立即对组件执行某些操作:
setState({value: ‘5’})
setState((state, props) => ({value: state.name + “‘s”}))
setState([object / function like above], () => {}) —— 这个表单允许我们附加 callback,当 state 显示我们想要的数据时被调用(在第一个参数)。
1import React, { Component } from 'react';
2
3export default class App extends Component {
4state = {
5name: 'Someone :)'
6}
7
8onClick = () => this.setState({ name: 'You' })
9
10render() {
11return (
12
13
14
15);
16}
17}
18
19const HelloWorld = (props) => Hello {props.name}
React Context
React 最近稳定的 Context API(已经在 React 中存在了相当长的时间,尽管被 Redux 等一些最受欢迎的库广泛使用,却是一个实验性功能)有助于我们解决一个问题:Props drilling。简而言之 Props drilling 是在结构中深入传递 props 的一种方式 —— 例如,它可以是组件的某种主题、针对特定语言的本地化、用户信息等。在 Context出现之前(或者更确切地说,在它变成非实验功能之前),它是通过递归方式从父级一直传递到到子级的最后一级的来进行钻取的(显然还有可以解决这个问题的 Redux)。请注意,此功能仅仅用于解决 Props drilling 的问题,并且不能替代 Redux 或 Mobx 等。当然如果你只使用状态管理库,则可以随意替把它替换掉。
总结
这是我们的React教程的第一部分。在后续的文章中,我们会设计更多高级主题,包括样式和类型检查,以及生产部署和性能优化。