这篇文章来自于Getting started with React. 以下是翻译原文.
React基本概念的概览与演示, 如components, state和props, 以及提交表单, 从API获取数据或者部署React 应用到生产环境.
自动我第一次学习JavaScript时就已经听说了React, 但是我要承认我看了它一眼就被它吓到了. 我看到它像混合着JavaScript的HTML, 这不是我们正要避免的吗? React有什么了不起的?
相反, 我专注于学习原生 JavaScript并在专业环境使用jQuery. 经历了几次入门React学习之后, 我终于学会了React, 而且我逐渐懂得了我为什么想使用React而不是原生JavaScript和jQuery.
我尝试着把我学到的所有东西精简为一个好的的介绍分享给你. 以下便是.
如果以前从没有使用过JavaScript和DOM, 在你开始使用React之前有一些东西你应该预先知道. 举例来说, 我会在使用React之前先熟悉它们.
这是我认为学习React的前提
React最重要的特征之一是, 你可以创建自定义组件, 可重用的HTML元素去快速且高效的构建UI. React还使用state和props流线化数据如何存储和处理.
我们将在文本中详细地介绍全部内容, 让我们开始吧.
有许多方法建立React, 我给你展示两种方法, 这样你就能知道它是如何工作的.
第一个方法不是建立React的流行方法并且不知道在我们的教程其余部分要做什么. 但是如果你使用过jQuery这样的库就很容易理解. 如果你不熟悉Webpack, Babel和Node.js, 这是最不可怕的入门方式.
让我们通过创建基本的index.html文件开始吧. 我们在head中加载三个CDNS—React, React DOM和Babel. 我们还要创建一个id为root的div标签, 最后我们创建一个script标签, 你的自定义代码将在其中.
<html>
<head>
<meta charset="utf-8">
<title>Hello React!title>
<script src="https://unpkg.com/react@16/umd/react.development.js">script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js">script>
<script src="https://unpkg.com/[email protected]/babel.js">script>
head>
<body>
<div id="root">div>
<script type="text/babel">
// React code will go here
script>
body>
html>
在写本文时, 我加载的是最新稳定版本的库.
我们应用程序的入口将是id为root的div标签, 这由习惯命名. 你将注意到text/babel的脚本类型, 这让我们强制使用Babel.
现在, 让我们写下React的第一个代码块. 我们将使用ES6的类去创建一个叫App的React组件.
class App extends React.Component {
//...
}
现在我们添加render() 方法, 在组建中这个唯一的一个必须要有的方法, 它常用来渲染DOM节点.
class App extends React.Component {
render() {
return (
//...
);
}
}
在return里面, 我们放置一个看起来简单的HTML元素. 注意我们没有在这里返回字符串, 因此不要在元素的周围使用引用. 这个叫做JSX, 我们在后面会学习更多JSX.
class App extends React.Component {
render() {
return (
Hello React!
);
}
}
最后, 我们使用React DOM 的render()方法去渲染我们在HTML中root元素中创建App类
ReactDOM.render( , document.getElementById('root'));
这是index.html的完整代码:
Hello React!
现在, 如果你在浏览器中查看你的index.html文件, 你将看到我们创建的h1标签被渲染为DOM.
漂亮! 现在你已经完成了它, 你可以明白入门React并不是特别可怕. 它只是我们能加载到HTML中的一些JavaScript辅助库.
我们这样做是为了演示, 但是从这里开始我们要使用另外一种方法: Create React App.
前面的方法我用来加载JavaScript库到静态HTML页面并渲染React和Babel, 它并不是有效率, 而且不易维护.
幸运的是, Facebook已经创建了Create React App环境, 它会预先配置你需要构建React App的所有东西. 它会创建一个现有的开发环境服务器, 并使用Webpack自动编译React, JSX和ES6, 自动为css文件加前缀, 自动用ESLint测试和警告代码中出现的错误.
使用create-react-app, 在你的终端上运行下面的代码, 在你想要的安装目录下运行. 确保你有5.2及更高的Node.js版本.
npx create-react-app react-tutorial
一旦完成安装, 进入新创建的目录并运行项目
cd react-tutorial
npm start
一旦你运行了上述命令, 新的窗口会在localhost:3000中弹出, 包含你新创建的React 应用.
如果你研究下项目的结构, 你将看到/public和/src目录, 伴随者node_modules, .gitignore, README.md和package.json.
在/public目录中, 我们的重要文件是index.html, 它和我们之前创建的静态index.html文件(只有一个id为root的div元素)很像. 现在, 没有库和脚本被加载. /src目录将包含我们的React代码.
去看看环境如何自动编译和更新你的React代码, 在/src/App.js中找到像这样的一行:
To get started, edit src/App.js
and save to reload.
并用其他文本替换. 当你保存文件后, 你会注意到localhost:3000使用新的数据编译和刷新.
接着删除/src目录下的所有文件, 并且我们创建自己的模板文件, 没有任何的多于. 我们只留下index.css和index.js
对于index.css, 我仅仅复制黏贴Primitive CSS中的内容到index.css中. 如果你想, 你也可以使用Bootstrap或者任何你想用的CSS框架, 或者啥也不用. 我发现它很容易工作.
现在在index.js中, 我们引入React, ReactDOM 和CSS文件.
// 目录: src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
让我们再一次创建我们的App组件. 再次之前, 我们已经有了, 但是现在我要添加一个带有class的div元素. 你注意到我使用了className而不是class. 这是我们第一个提示, 在这里被写下的代码是JavaScript, 不是真正的HTML.
class App extends Component {
render() {
return (
Hello, React!
);
}
}
最终, 我们和之前一样渲染App到root元素下.
ReactDOM.render( , document.getElementById('root'));
下面是我们完整的index.js. 此时, 我们加载Component为React的一个属性, 因此我们不在需要扩展React.Component了.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './index.css';
class App extends Component {
render() {
return (
Hello, React!
);
}
}
ReactDOM.render( , document.getElementById('root'));
如果你回到localhost:3000, 你将像以前一样看到"Hello, React!". 我们现在有了一个React应用程序了.
这里有一个叫做React Developer Tools的扩展, 它们让你在使用React时更加容易. 下载React Developer for Chrome, 在你喜欢的任意浏览器上运行它.
在你安装好之后, 当你打开控制台时, 你将看见React标签. 点击它后你就可以了在写组件时检查它们. 你还可以到Elements标签看真实的DOM输出. 目前者不像是一个好的处理, 但是随着应用程序变得越来越复杂时, 它将变得越来越有使用的必要了.
现在我们有了所有的工具和设置, 我们可以去使用React了.
如你所见, 我们已经在React代码中使用过看起来像HTML的代码, 但是它并不是HTML. 它是JSX, 代指JavaScript和XML.
使用JSX, 我们可以写出像HTML的代码, 而且我们可以创建并使用像XML的标签. 以下赋值给一个变量的JSX.
const heading = Hello, React
;
写React并不是必须要使用JSX. 在引擎中, 它运行createElement, 它可以接收标签, 带有属性的对象, 组件的子级以及渲染相同的信息. 下面的代码与上面使用的JSX有着同样的输出.
const heading = React.createElement(
'h1',
{className: 'site-heading'},
'Hello, React!'
);
JSX特别接近于JavaScript, 而不是HTML, 因此在写JSX时有一些关键的区别:
/
)结束—例如: ![]()
JavaScript表达式必须嵌入在JSX的大括号中, 包括变量, 函数和属性.
const name = 'Tania';
const heading = Hello, {name}
;
比起创建并添加元素到原生的JavaScript, JSX更容易书写和理解. 这也是人们喜欢使用React的原因之一.
到目前为止, 我们已经创建了一个组件——App组件. 几乎React中所有的东西都由组件组成, 组件可以是类组件或者简单组件.
大部分React应用程序有许多的组件, 所有东西加载到重要的App组件中. 组件也经常获得它们自己的组件, 所以让我们按照那样来修改我们的项目.
从index.js中移除App类, 它看起来像这样:
// 目录: src/App.js
import React, {Component} from 'react';
class App extends Component {
render() {
return (
Hello, React!
);
}
}
export default App;
我们导出该组件为App并加载它到index.js中. 将文件分割成组件并不是必须的, 但是如果你不这样做, 应用程序会变得笨拙且不可控制.
让我们创建另一个组件. 我们准备创建一个表格. 新建Table.js, 并用下面的数据填充它.
// 目录: src/Table.js
import React, {Component} from 'react';
class Table extends Component {
render() {
return (
Name
Job
Charlie
Janitor
Mac
Bouncer
Dee
Aspiring actress
Dennis
Bartender
);
}
}
export default Table;
我们创建的这个组件是一个自定义的类组件. 我们利用自定义组件区分它们和普通额HTML元素. 回到App.js, 我们要加载Table, 首先要引入它.
import Table from './Table';
然后将其加载到App的render()函数中, 之前这个函数有我们的"Hello, React!". 我还改变了外部容器的类(className)
return (
);
如果你回顾下生产环境, 你讲看到被加载的Table.
现在我们看见的是自定义类组件. 我们可以复用这个组件. 然而, 由于数据是写死的, 它到时候不会特别有用.
React中的另一中组件是简单组件, 它是一个函数. 这个组件不使用class关键字. 让我们使用Table创建两个简单组件——一个table header, 一个table body.
我们将使用ES6的箭头函数创建这些简单组件. 首先, 这是table header:
const TableHeader = () => {
return (
Name
Job
);
}
这是table body:
const TableBody = () => {
return (
Charlie
Janitor
Mac
Bouncer
Dee
Aspiring actress
Dennis
Bartender
);
}
现在我们的Table看起来像这样:
class Table extends Component {
render() {
return (
);
}
}
所有东西都会像以前一样出现. 如你所见, 组件可以嵌入到其他组件中, 而且简单组件和类组件可以混合使用.
一个类组件必须有render()函数, 而且return 只能返回一个父元素.
总的来说, 让我们比较下简单组件和类组件.
// simaple component
const SimpleComponent = () => {
return Example;
}
//class component
class ClassComponent extends Component {
render() {
return Example;
}
}
注意如果return只有一行, 则可没有小括号.
现在, 我们有一个Table组件, 但是数据是写死的. React中的重要特点之一便是如何处理数据, 它用被称作为props的属性和state来处理数据. 首先, 我们先专注于使用props处理数据.
首先, 让我们从TableBody组件中移除所有数据.
//Table.js
const TableBody = () => {
return ;
}
然后让我们移动所有数据到一个对象数组中, 就像我们引入了一个基于JSON的API. 我们必须在render()中创建这个数组.
//App.js
class App extends Component {
render() {
const characters = [
{
'name': 'Charlie',
'job': 'Janitor'
},
{
'name': 'Mac',
'job': 'Bouncer'
},
{
'name': 'Dee',
'job': 'Aspring actress'
},
{
'name': 'Dennis',
'job': 'Bartender'
}
];
return (
);
}
}
现在, 我们传入数据.到带有属性的子组件(Table), 你也可以使用data-属性传递数据. 我们可以调用我们想用的任意属性, 只要它不是关键字, 因此我将使用characterData. 我传递数据是characters变量, 而且我在它两边加入大括号因为它是一个JavaScript表达式.
return (
);
现在数据传递到了Table中, 我们必须从另一方面访问它.
//Table.js
class Table extends Component {
render() {
const { characterData } = this.props;
return (
);
}
}
如果你打开React DevTools并检查Table组件, 你将在属性中看到包含数据的数组.这里存储的数据被称作虚拟DOM, 它是同步数据到真实DOM的一种高效的方法.
但是, 这个数据还不在真实的DOM中. 在Table里, 我们可以通过this.props访问所有props. 我们只传递一个prop, characterData, 因此我们将使用this.props.characterData来找到这个数据.
我将使用ES6简写属性创建包含this.props.characterData的变量.
const { characterData } = this.props;
由于我们的Table组件事实上由两个较小的简单组件组成, 我们再一次通过props将其传递到TableBody组件中.
// Table.js
class Table extends Component {
render() {
const { characterData } = this.props;
return (
);
}
}
现在, TableBody不带参数并返回一个单一的标签.
const TableBody = () => {
return ;
}
我们传递props作为参数, 并通过映射数组返回数组中每个对象的表行. 这个映射将包含在一个rows变量中, 这个变量作为表达式返回.
const TableBody = props => {
const rows = props.characterData.map((row, index) => {
return (
{row.name}
{row.job}
);
});
return {rows};
}
如果你浏览应用程序的前端, 现在所有的数据都正在加载.
你会注意到我已经给每个表行添加了一个键索引. 你在React中创建列表时应该使用keys, 因为它们能识别每个列表项. 当我们想处理列表项时, 我们会理解这是多么的必要.
props是传递已知数据给一个React组件的高效方式, 然而组件不能改变props——它们是只读的. 在下一节中, 我们将在React中学习如何使用state进一步控制数据的处理.
现在, 我们存储character数据在一个数组变量中, 将其作为props传递. 这是好的开始, 但是想一想如果我们想要从数组中删除数组项该如何. 使用props, 我们就有一种单向数据流, 但是我们可以从组件中更新私有数据.
你可以把state考虑作为任意数据, 这个数据可被存储和修改且没有必要添加到数据库中——例如, 在确认购买之前, 添加或删除你的购物车.
首先, 我们创建一个state对象.
class App extends Component {
state = {};
这个对象包含你想在state中存储的所有东西. 对我们来说, 它就是characters.
class App extends Component {
state = {
characters: []
};
移动我们之前创建的对象中的所有数组到state.characters中.
class App extends Component {
state = {
characters: [
{
'name': 'Charlie',
// the rest of the data
]
};
我们的数据是正式包含在state中了. 因为我们想从table中移除一个character, 我们可以在父级的App类创建一个removeCharacter方法.
为了找到这个state, 我们使用this.state.characters, 这和之前的ES6方法一样. 为了更新state, 我们使用this.setState(), 这是一个为操纵state而内置的方法. 我们基于传递的index过滤数组, 并返回新数组.
你必须使用this.setState()来改变一个数组. 简单的应用一个新值给this.state.property不会工作.
//App.js
removeCharacter = index => {
const { characters } = this.state;
this.setState({
characters: characters.filter((character, i) => {
return i !== index;
})
});
}
filter不会变异而是创建一个新数组, 在JavaScript中这是一个更改数组的好方法. 这个特定的方法是测试一个索引相对于数组中的所有索引, 并返回除传递之外的所有索引.
现在我们要传递函数给组件, 在每个调用此函数旁边渲染一个按钮. 我们传递removeCharacter函数作为prop给Table.
//App.js
return (
);
由于我们将其从Table向下传递到TableBody, 我们再次将其作为prop传递, 就像我们对character 数据做的一样.
//Table.js
class Table extends Component {
render() {
const { characterData, removeCharacter } = this.props;
return (
);
}
}
这里是我们在removeCharacter()方法中定义的索引进入的地方. 在TableBody组件中国, 我们传递key/index作为参数, 因此过滤函数知道哪个数组项该移除. 我们创建一个带有onClick的按钮并传递它.
//Table.js
{row.name}
{row.job}
onClick函数必须传递一个返回removeCharacter方法的函数, 否则它将尝试自动运行
太棒了. 现在我们有了删除按钮, 而且我们可以通过删除character修改我们的state.
我删除了Mac.
现在你应该理解state如何初始化, 它是如何修改的.
现在我们的数据存储在状态(state)中, 我们可以从state中移除任意数据项. 但是, 如果我们想要添加新数据怎么办? 在现实世界中的应用程序, 你更有可能从空state开始并向其中添加数据, 比如to-do列表或者购物车.
首先, 让我们一出state.characters中的所有写死的数据, 现在我们来通过表单更新它.
class App extends Component {
state = {
characters: []
};
让我们继续在Form.js文件中创建Form组件. 我们创建一个类组件, 并在内部使用constructor(), 到目前为止我们还没有做这一步. 我们需要在constructor()中使用this接收父级传递的props.
我们开始设置Form中的state初始值为带有空属性的对象, 并将初始state赋值给this.state.
//Form.js
import React, { Component } from 'react';
class Form extends Component {
constructor(props) {
super(props);
this.initialState = {
name: '',
job: ''
};
this.state = this.initialState;
}
}
我们对于表单的目标是在表单中的字段改变时可以时刻更新Form中的state. 当我们提交时, 所有的数据传递给App的state, 之后会更新Table.
首先, 们将创建函数, 它在在每次对输入进行更改时运行. event将传递, 我们设置Form中的state使input之拥有name(key)和value.
handleChange = event => {
const {name, value} = event.target;
this.setState({
[name] : value
});
}
让我们在提交表单之前完成它. 在渲染时, 让我们从state中获取两个属性, 并将其作为值分配给对应的表单键. 我们将运行handleChange()并作为input的onChange属性, 最后我们将会到处Form组件.
render() {
const { name, job } = this.state;
return (
);
}
export default Form;
在App.js中, 我们在table下渲染表单.
//App.js
return (
);
如果我们现在进入应用程序的前端, 我们将看到一个没有提交的表单. 更新字段后你将看见本地状态(state)正在更新.
很酷, 最后一步是允许我们实际地提交表单并更新父级状态(state). 我们在App上创建一个叫handleSubmit()的函数, 这个函数使用现有的this.state.characters更新state, 并使用ES6扩展运算符添加新的character参数.
//App.js
handleSubmit = character => {
this.setState({characters: [...this.state.characters, character]});
}
让我们确保我们将其作为参数传递给了Form.
现在在Form中, 我们将创建一个叫submitForm()的方法调用那个函数. 将Form的state作为参数传递给我们之前定义的character. 这还会设置state为初始state, 在提交表单后清空表单.
//Form.js
submitForm = () => {
this.props.handleSubmit(this.state);
this.setState(this.initialState);
}
最终, 我们添加一个提交按钮用于提交表单. 我们使用onClick而不是onSubmit. 因为我们没有使用标准的提交功能. 点击会调用我们穿件的submitForm函数.
这就是react应用程序. 这个应用程序完成了. 我们可以创建, 添加, 移除表格中的用户. 由于Table和TableBody已经从state中获取到了值, 它将正确的显示.
如果你在学习React上没有头绪, 你可以看看github上的完整源码.
一个普遍的React的用法是从API中获取数据. 如果你熟悉API是什么或者如何连接它, 我建议你读一下如何用JavaScript连接到API, 这能让你理解API是什么, 并知道如何用普通的JavaScript使用它.
有一个小测试, 我们创建一个Api.js文件, 再在这里创建一个新的App. 我们可以测试一个公共的API是维基百科的API, 我还有一个URL 端点用于随机搜索. 你可以到那个链接看API——确保你在你的浏览器中安装了JSONView.
我们使用JavaScript内置的Fetch方法从URL端点获取数据并显示. 你可以通过引入import App from './Api’的index.js中的URL来切换我们创建的应用程序和测试文件.
我不准备一行一行的解释这段代码, 因为这和我们已经学到的创建组件, 渲染及通过state数组映射y一样. 有个新的代码是componentDidMount(), 它是React生命周期的一个方法. 生命周期是方法在React中调用的顺序. Mounting指明某一项插入到DOM中.
当我们获取了API中的数据, 我们想要使用componentDidMount, 因为我们想要确保在获取数据之前组件已经渲染到了DOM中. 在下面的片段中, 你将看见我们如何从Wikipedia API中获取数据, 并显示在页面中.
// Api.js
import React, { Component } from 'react';
class App extends Component {
state = {
data: []
};
// Code is invoked after the component is mounted/inserted into the DOM tree.
componentDidMount() {
const url = "https://en.wikipedia.org/w/api.php?action=opensearch&search=Seona+Dancing&format=json&origin=*";
fetch(url)
.then(result => result.json())
.then(result => {
this.setState({
data: result
})
});
}
render() {
const { data } = this.state;
const result = data.map((entry, index) => {
return {entry} ;
});
return {result}
;
}
}
export default App;
当你在本地服务器上保存并运行这个文件, 你将看见Wikipedia API数据呈现在了DOM中.
还有其他生命周期方法, 但是详细介绍它们会超出本文的主旨. 你可以在这里阅读更多的React 组件.
目前未知我们所做的一切都发生在开发环境. 我们一直编译, 热加载和更新. 在生产环境中, 我们要统计加载中的文件——不是源文件. 我们可以通过构建和部署去做.
现在, 如果你指向编译所有的React代码并将其放在根目录下某个地方, 你需要是运行下面一行:
npm run build
这会创建一个包含你的应用程序的文件夹. 把这个文件的内容放在任何地方, 你就做完了.
我们还可以再做一步, 用npm为我们部署. 我们去构建github页面, 因此你已经熟悉Git并且在github上获取你的代码.
确保你退出了你本地的React环境, 即代码目前没有运行. 首先, 我们添加一个homepage字段到package.json, 哪里有我们想要运行应用程序的URL.
"homepage": "https://taniarascia.github.io/react-tutorial",
我们在添加两行到scripts属性中:
"scripts": {
// ...
"predeploy": "npm run build",
"deploy": "gh-pages -d build"
}
在你的项目中, 你将gh-pages添加到devDependencies中去.
npm install --save-dev gh-pages
我们创建包含所有已编译的静态的文件的build目录.
npm run build
最终, 我们部署到gh-pages上.
npm run deploy
我们完成了!这个应用程序现在在https://taniarascia.github.io/react-tutorial.
这篇文章很好的给你介绍了React, 简单/类组件, state, props, 处理表单数据, 从API中获取数据和部署应用程序. 有许多需要学习并用React实践, 我希望你现在有信心升入学习并实践React.
View Source on GitHub
View Project