这是一篇算React入门级的文章,初次接触React,有问题请指出~~~。
这里介绍搭建一个 react 项目的两种方式: CDN链接 和 官方推荐的 create-react-app 脚手架
创建一个 index.html 文件,将 React、React DOM、Babel 这三个 CDN 链接引入到 head 标签中。并创建一个 div ,id为 root,作为根元素。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello React</title>
<!-- React --- React顶级API -->
<script src="https://unpkg.com/react@^16/umd/react.production.min.js"></script>
<!-- React DOM --- 添加特定与DOM的方法 -->
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<!-- Babel ---js编译器,可使用es6+ -->
<script src="https://unpkg.com/[email protected]/babel.js"></script>
</head>
<body>
<div id="root"></div>
<!-- text/babel 脚本类型是babel所必须的 -->
<script type="text/babel">
// react code
</script>
</body>
</html>
引入这三个 CDN 链接的目的:
如果需要安装指定的版本,将@后面的版本号改为指定版本。
⚠️注意:script 脚本的类型 text/babel
是必须的。
我们都知道,不管是 Vue 还是 React 都是组件化,在 React 中有两种创建组件的方法,Class
组件和 Function
组件, 这里我们创建一个 Class 组件。
创建一个 Class 组件很简单,只要创建一个 Class
类,并让他继承 React.Component
, 在render()
方法中 return
一个 JSX
模版,用于呈现 DOM节点。
⚠️注意:render() 方法是类组件唯一需要的方法。
创建组件App :
// 创建组件App
class App extends React.Component {
// render方法必须,用于呈现DOM节点
render() {
return (
// jsx
<h1>hello word</h1>
)
}
}
在 return
中, 可以看到返回很像 HTML 的内容,但其不是 HTML ,而是 JSX ,这里先不过多介绍。
最后,我们使用 ReactDOM.render()
方法将 App组件渲染到 root
根元素 div 中:
ReactDOM.render(<App/>, document.getElementById('root'));
index.html 的完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello React</title>
<!-- React --- React顶级API -->
<script src="https://unpkg.com/react@^16/umd/react.production.min.js"></script>
<!-- React DOM --- 添加特定与DOM的方法 -->
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<!-- Babel ---js编译器,可使用es6+ -->
<script src="https://unpkg.com/[email protected]/babel.js"></script>
</head>
<body>
<div id="root"></div>
<!-- text/babel 脚本类型是babel所必须的 -->
<script type="text/babel">
// 创建组件App
class App extends React.Component {
// render方法必须,用于呈现DOM节点
render() {
return (
// jsx
<h1>hello word</h1>
)
}
}
ReactDOM.render(<App/>, document.getElementById('root'));
</script>
</body>
</html>
这样一个简单的 React 应用就搭建好了。
第一种搭建 react 项目的方法只适合书写简单的react 代码,使用 create-react-app
脚手架可以帮我们初始化一个项目 demo。
npx create-react-app react-start
项目结构:public/index.html 是页面模板,src/index.js 则是入口JS文件
cd react-start
npm start
下面修改一下代码,修改App.js 文件为上面创建 App 组件的代码:
import React, { Component } from 'react';
// import './App.css';
class App extends Component {
render() {
return <h1>Hello, React</h1>;
}
}
export default App;
上面的代码中已经接触了 JSX,看起来很像 HTML ,但是并不是。它是JSX,代表 JavaScript XML 。
接着上面的代码,修改App.js :
const name = 'React';
const element = (
<div tabIndex="0" className="box">
<h1>Hello, {name}</h1>
<div className="name">
<span>xiaoqi</span>
</div>
</div>
);
class App extends Component {
render() {
return element;
}
}
element 就是一个 JSX,可以看到它的特点:
tabIndex
、className
看一下渲染之后的效果:
元素是构成 React 应用的最小砖块。
创建一个元素 element :
const element = (
<div className="box">
<h1>React 入门</h1>
<span> 元素是构成 React 应用的最小砖块。</span>
</div>
);
将这个元素渲染到 DOM 根结点中,需要使用 ReactDOM.render()
方法:
ReactDOM.render(element, document.getElementById('root'));
因 index.js 文件是项目的 js 入口文件,所以我们修改 index.js 文件为上面的代码:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// import App from './App';
// import * as serviceWorker from './serviceWorker';
const element = (
<div className="box">
<h1>React 入门</h1>
<span> 元素是构成 React 应用的最小砖块。</span>
</div>
);
ReactDOM.render(element, document.getElementById('root'));
组件分为两种: Class 组件和 Function 组件。
大多数的 React 应用都由很多小组件组成,将所有的小组件都添加到App主组件中,最后将 App主组件渲染到入口 js 文件 index.js 中。
我们将修改 App.js
和 index.js
文件:
App.js:
import React, { Component } from 'react';
class App extends Component {
render() {
return (
<div className="app">
<h1> Hello, React</h1>
</div>
);
}
}
export default App;
index.js:
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './index.css'
ReactDOM.render(<App />, document.getElementById('root'))
前面提过,创建一个 Class 组件,只需要创建一个 class 类,并且让它继承React.Component
, 在 render()
方法中 return
出 JSX
模版。
接下来,我们在 src 文件夹中 ,创建一个 Table.js文件:
// Table组件
import React, { Component } from 'react';
class Table extends Component {
render() {
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Job</th>
</tr>
</thead>
<tbody>
<tr>
<td>Charlie</td>
<td>Janitor</td>
</tr>
<tr>
<td>Mac</td>
<td>Bouncer</td>
</tr>
<tr>
<td>Dee</td>
<td>Aspiring actress</td>
</tr>
</table>
);
}
}
export default Table;
在 App.js中添加 Table组件:
class App extends Component {
render() {
return (
<div className="app">
<h1> Hello, React</h1>
<Table />
</div>
);
}
}
运行结果:
上面的 Table 组件, 我们可以拆分为两个子组件:TableBody
组件和TableHeader
组件, 这两个组件我们使用 Function 组件来创建:
修改 Table.js : TableHeader
和 TableBody
组件都在Table.js文件中,并被 Table
组件使用。
// TableHeader组件
const TableHeader = () => {
return (
<thead>
<tr>
<th>Name</th>
<th>Job</th>
</tr>
</thead>
);
};
// TableBody 组件
const TableBody = () => {
return (
<tbody>
<tr>
<td>Charlie</td>
<td>Janitor</td>
</tr>
<tr>
<td>Mac</td>
<td>Bouncer</td>
</tr>
<tr>
<td>Dee</td>
<td>Aspiring actress</td>
</tr>
</tbody>
);
};
class Table extends Component {
render() {
return (
<table>
<TableHeader />
<TableBody />
</table>
);
}
}
比较一下 Function
组件与Class
组件:
Function 组件:
const functionComponent = () => {
return <div> Example</div>
}
Class 组件:
class ClassComponent entends React.Component {
render() {
return <div> Example</div>
}
}
Vue中的父传子使用的是 props,React 中的父传子也是props,下面看一下在React中props是如何使用的。
如果表格的数据很多,那么上面的TableBody
组件会显的很臃肿。我们将< tbody>中要输出的数据提取出来,通过props的方式来进行传递。
修改App.js文件:
在App组件中声明数据 characters
, 表示< tbody>中要输出的数据。
在子组件 Table
上添加属性名称和数据
class App extends Component {
render() {
// 提取数据
const characters = [
{
name: 'Charlie',
job: 'Janitor',
},
{
name: 'Mac',
job: 'Bouncer',
},
{
name: 'Dee',
job: 'Aspring actress',
},
];
return (
<div className="app">
<h1> Hello, React</h1>
<Table characterData={characters} />
</div>
);
}
}
修改 Table.js :
子组件中的使用:在 class 组件中通过 this.props.属性名
来获取父组件中传递的数据:
class Table extends Component {
render() {
const { characterData } = this.props;
return (
<table>
<TableHeader />
<TableBody characterData={characterData} />
</table>
);
}
}
这里的 const { characterData } = this.props
相当于 const characterData = this.props.characterData
,这里只是es6的写法。
继续将数据传递给 Table
组件的子组件 TableBody
: TableBody
是一个 function组件,其使用 props 的方法直接是:将props作为参数名传递给function组件,使用 props.属性名
即可获取父组件的数据。
// TableBody 组件
const TableBody = (props) => {
const rows = props.characterData.map((item, index) => {
return (
<tr key={index}>
<td>{item.name}</td>
<td>{item.job}</td>
</tr>
);
});
return <tbody>{rows}</tbody>;
};
上面的例子中,给每个表行都添加了一个 key
。React 中创建列表时,使用都要使用key
,来识别列表的每一项项,这是必要的。
⚠️注意:props
具有只读性,无论是函数声明还是class声明的组件,都不能修改自身的props
props 是单向传递,并且是只读的。如果想改变数据的状态,可以使用state,与props 不同的是state可变,但是 state 是class组件私有的对象,修改state 不能直接修改,要使用this.setState()
方法来改变。
现在我们实现一个功能:在表的每行添加一个删除按钮,点击之后,可删除该行信息。
修改 App.js
state
对象,包含存储的数据 charactersremoveCharacter()
方法,在该方法内实现 state 数据的变化,通过数组的索引过滤,然后返回新的数组。Table
class App extends Component {
state = {
characters: [
{
name: 'Charlie',
job: 'Janitor',
},
{
name: 'Mac',
job: 'Bouncer',
},
{
name: 'Dee',
job: 'Aspring actress',
},
],
};
// 箭头函数解决 class中this不绑定的问题
removeCharacter = (i) => {
this.setState({
characters: this.state.characters.filter((item, index) => {
return i !== index;
}),
});
};
render() {
// 提取数据
return (
<div className="app">
<h1> Hello, React</h1>
<Table
characterData={this.state.characters}
removeCharacter={this.removeCharacter}
/>
</div>
);
}
}
将函数removeCharactor
传递给Table
组件之后,还需要传递给TableBody
组件,并且还要在 TableBody
组件中给每行添加一个按钮,点击之后执行此函数。
修改 Table.js:
// Table组件
import React, { Component } from 'react';
// TableHeader组件
const TableHeader = () => {
return (
<thead>
<tr>
<th>Name</th>
<th>Job</th>
</tr>
</thead>
);
};
// TableBody 组件
const TableBody = (props) => {
const rows = props.characterData.map((item, index) => {
return (
<tr key={index}>
<td>{item.name}</td>
<td>{item.job}</td>
<td>
<button onClick={() => props.removeCharacter(index)}>Dalete</button>
</td>
</tr>
);
});
return <tbody>{rows}</tbody>;
};
class Table extends Component {
render() {
const { characterData, removeCharacter } = this.props;
return (
<table>
<TableHeader />
<TableBody
characterData={characterData}
removeCharacter={removeCharacter}
/>
</table>
);
}
}
该onClick函数必须通过返回该removeCharacter()方法的函数,否则它将尝试自动运行。
⚠️注意: State 的更新可能是异步的, React 可能会把多个setState() 调用合并成一个调用。
React 中事件处理和 DOM 元素的很相似,但是在语法上有些不同:
例子:
<button onClick={activateLasers}>
Activate Lasers
</button>
前面,我们已经实现了将数据存储在state状态中,并且可以从状态中删除任何数据。但如果我们想添加新的数据怎么办呢?
接下来,通过下面的表单例子,来体验 React 中的事件处理。我们添加一个 Form.js 组件, 每次更改表单中字段时会更新状态,并且在我们提交时,所有数据会传递给 App组件的 state,进而更新整个 Table。
创建新文件 Form.js :
我们将表单的初始状态设置为带有空属性的对像,并且将初始状态赋给 this.state
:
import React, { Component } from 'react'
class Form extends Component {
initialState = {
name: '',
job: '',
}
state = this.initialState
}
表单内容大致如下:
render() {
const { name, job } = this.state; // 初始化时为空
return (
<form>
<label htmlFor="name">name</label>
<input
type="text"
name="name"
id="name"
value={name}
onChange={this.handleChange}
/>
<label htmlFor="job">job</label>
<input
type="text"
name="job"
id="job"
value={job}
onChange={this.handleChange}
/>
</form>
);
}
每次输入时都更改 state
里面的数据,所以在onChange
事件发生时,将 handleChangr()
方法作为回调传入
handleChange = (event) => {
const { name, value } = event.target;
this.setState({
[name]: value,
});
};
⚠️注意:
在class 组件中,class 的方法默认不会绑定 this
,所以要谨慎 JSX 回调函数中的 this 。例如上面的例子中,如果忘记绑定 this
,直接将this.handleChange
传入 onChange
,这时调用这个函数时, this
将会是是undefined
。
第一种:class 的方法使用箭头函数, 如上例。
handleChange = (event) => {
const { name, value } = event.target;
this.setState({
[name]: value,
});
};
第二种: 在构造函数中使用 this.handleChange.bind(this)
进行绑定。
constructor() {
super();
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const { name, value } = event.target;
this.setState({
[name]: value,
});
}
第三种:其实也相当于第二种,只不过不使用构造函数
handleChange(event) {
const { name, value } = event.target;
this.setState({
[name]: value,
});
// 直接在传入时进行绑定
onChange={this.handleChange.bind(this)}
前面实现了表单的数据更新,最后一步是提交表单的数据并更新App组件的 state
状态。
修改App.js:
创建一个名为handleSubmit
的函数,用来更新 state
的数据(添加表单提交的数据):
handleSubmit = (character) => {
// 添加数据
this.setState({
characters: [...this.state.characters, character],
});
};
将其传给 Form 组件:
render() {
// 提取数据
return (
<div className="app">
<h1> Hello, React</h1>
<Table
characterData={this.state.characters}
removeCharacter={this.removeCharacter}
/>
<Form handleSubmit={this.handleSubmit} />
</div>
);
}
接下来,在 Form.js 中创建名为 submitForm()
,该方法将调用 父组件传递的方法 handleSubmit
。将Form组件的 state
作为参数传入:
修改 Form,js:
// 提交表单
submitForm = () => {
// 添加数据
this.props.handleSubmit(this.state);
// 添加之后清空state
this.setState(this.initialState);
};
最后,我们为表单添加一个提交按钮用来提交表单:
<input type="button" value="submit" onClick={this.submitForm} />
⚠️注意:这里的submitFrom
方法也得绑定this
。
React 中的条件渲染和javaScript 中的一样,可以使用 运算符 if 或者条件运算。
下面实现一个登录/登出按钮,根据不同的状态现实不同的效果。为了方便,还是在上面的项目实现这效果。
添加一个Login.js文件:
创建两个组件:LoginButton
和 LogoutButton
分别代表登录和注销。
// 登录
const LoginButton = (props) => {
return <button onClick={props.click}>Login</button>;
};
// 注销
const LogoutButton = (props) => {
return <button onClick={props.click}>Logout</button>;
};
创建一个有状态的组件LoginControl
,用来根据当前的状态来渲染上面的两个组件。
class LoginControl extends Component {
// 初始状态
state = {
isLoggedIn: false,
};
handleLoginClick = () => {
this.setState({
isLoggedIn: true,
});
};
handleLogoutClick = () => {
this.setState({
isLoggedIn: false,
});
};
render() {
const isLoggedIn = this.state.isLoggedIn;
let button;
// 通过if 语句来进行条件渲染
if (isLoggedIn) {
button = <LoginButton click={this.handleLogoutClick} />;
} else {
button = <LogoutButton click={this.handleLoginClick} />;
}
return (
<div>
{button}
</div>
);
}
}
// 登录
const LoginGreeting = () => {
return <span>Welcome </span>;
};
// 注销
const LogoutGreeting = () => {
return <span>Please sign up</span>;
};
Geeting
,来渲染上面的描述组件.这里使用三目运算符来实现const Greeting = (props) => {
const { isLoggedIn } = props;
return <div>{isLoggedIn ? <LoginGreeting /> : <LogoutGreeting />}</div>;
};
LoginControl
组件中加入这个 Greeting
组件,同时在App 组件中加入LoginControl
组件。// ...
return (
<div>
{button}
<Greeting isLoggedIn={isLoggedIn} />
</div>
// ...
在App
加入LoginControl
组件:
// ...
render() {
// 提取数据
return (
<div className="app">
<h1> Hello, React</h1>
<Table
characterData={this.state.characters}
removeCharacter={this.removeCharacter}
/>
<Form handleSubmit={this.handleSubmit} />
<LoginControl />
</div>
);
}
// ...
运行项目(样式有点丑~):
前面的例子 TableBody
组件中 ,已经接触了列表:
const TableBody = (props) => {
const rows = props.characterData.map((item, index) => {
return (
<tr key={index}>
<td>{item.name}</td>
<td>{item.job}</td>
<td>
<button onClick={() => props.removeCharacter(index)}>Dalete</button>
</td>
</tr>
);
});
return <tbody>{rows}</tbody>;
};
这个组件中,rows
为 tr
的数组,最后将rows
插入到 < tbody>
元素中。
这里可以看到每一个 tr
都有一个特殊的属性key
,这个key
是必要的(这与diff算法有关)。
key
帮助 React
识别哪些元素改变了,比如删除或者添加。所以应当给数组中的每一个元素给予一个确定的标识。一个元素的key
最好是这个元素在列表中拥有的独一无二的字符串。通常使用数据的id
来作为元素的 key
。如果元素没有确定的 id
时,可以使用元素索引 index
作为 key
。
⚠️注意:
key
只有放在就近的数组上下文中才有意义。如上例中的map 方法中的元素需要设置 key 属性key
值只是在兄弟节点间独一无二,而不是全局的唯一。(也就是说,生成两个不同的数组时,可以使用相同的key
)当组件实例被创建并插入 DOM中,其生命周期调用顺序如下:
state
等static
属性getSnapshotBeforeUpdate
在render
之后componentDidUpdate
之前输出,类似于中间件用来做一些捕获操作getSnapshotBeforeUpdate
,有三个参数prevProps
,prevState
,snapshot
,表示之前的 props
,之前的 state
,和snapshot
。snapshot
是getSnapshotBeforeUpdate
返回的值修改 App.js 代码:
class App extends Component {
constructor() {
super();
console.log('1: constructor初始化');
}
state = {
characters: [
{
name: 'Charlie',
job: 'Janitor',
},
{
name: 'Mac',
job: 'Bouncer',
},
{
name: 'Dee',
job: 'Aspring actress',
},
],
};
// 移除项目
removeCharacter = (i) => {
this.setState({
characters: this.state.characters.filter((item, index) => {
return i !== index;
}),
});
};
handleSubmit = (character) => {
// 添加数据
this.setState({
characters: [...this.state.characters, character],
});
};
static getDerivedStateFromProps(nextProps, prevState) {
console.log('getDerivedStateFromProps: 组件将要被更新');
console.log(prevState);
return true;
}
componentDidMount() {
console.log('componentDidMount 组件挂载完毕');
}
getSnapshotBeforeUpdate(preProps, prevState) {
console.log(preProps, prevState);
return 'top: 200';
}
componentDidUpdate(preProps, prevState, snapshot) {
console.log('组件更新完毕');
console.log(preProps, prevState, snapshot);
}
render() {
// 提取数据
return (
<div className="app">
<h1> Hello, React</h1>
<Table
characterData={this.state.characters}
removeCharacter={this.removeCharacter}
/>
<Form handleSubmit={this.handleSubmit} />
<LoginControl />
</div>
);
}
}
学习React很多都是以Vue的思路先入个门,很多东西都是类似的。第一篇入门文章就到这里,后面继续~~。