安装方式
使用create-react-app脚手架可以帮我们初始化一个项目demo
npx create-react-app my-app
项目结构:public/index.html是页面模板,src/index.js是入口JS文件
安装完成后,进入项目文件并启动
cd react-start
npm start
运行后会弹出react图标窗口。
下面将App.js文件修改一下:
import React, { Component } from 'react';
// import './App.css';
class App extends Component {
render() {
return <h1>Hello, React</h1>;
}
}
export default App;
此时react图标就会变成文字:Hello,React
基本元素介绍
什么是JSX
在上面的代码中使用了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,可以看到它的特点:
- 可以使用{}内置js表达式
- JSX标签可以包含很多子标签
- 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文件:
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中。
Class组件
创建一个Class组件只需要创建一个class类,并且继承React.Component,在render()方法中return出JSX模板。
接下里在scr文件夹中创建一个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主组件中:
class App extends Component {
render() {
return (
<div className="app">
<h1> Hello, React</h1>
<Table />
</div>
);
}
}
Function组件
上面的Table组件可以拆分为两个子组件:TableHeader和TableBody,这两个组件可以使用Function组件来创建:
// 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>
}
}
组件通信
React使用props传递数据。
如果表格的数据很多,那么上面的TableBody组件会显得很臃肿。因此可以将tbody中要输出的数据提取出来,通过props的方式进行传递。
修改App.js文件:在App组件中生命数据charaters,表示中要输出的数据。在子组件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中通过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的写法。
继续将数据传递给TableBody组件。由于TableBody是一个function组件,所以其接收数据的方式跟class组件是不一样的。其使用props的方法直接是将props作为参数名传递给function组件:
// 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来识别列表的每一项。
State状态
props是单向传递,并且是只读的。如果想改变数据的状态,可以使用state,与props不同的是,state可变,但是state是class组件私有的对象。
修改state不能直接修改,要使用this.setState()
方法来改变。
下面实现一个功能:在每行后面添加一个删除按钮,点击之后可以删除该行。继续修改App.js:
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>
);
}
}
通过state来传递数据,并通过setState方法来改变数据。
这里将removeCharacter传递给Table组件后,还需要继续传给TableBody组件,并且还需要在TableBody组件中给每行添加一个按钮,点击之后触发此函数:
// 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)}>Delete</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>
);
}
}
注意:State的更新可能是异步的,React可能会把多个setState()调用合并成一个调用。
props与state的区别
- state是管理数据,控制状态,可通过this.setState()改变
- props是外部传入的数据参数,不可变
- 没有state的叫无状态组件,有state的叫有状态组件
- 多用props,少用state
事件处理
React中的事件处理和DOM元素的很相似,但在语法上有不同:
- React事件的命名采用小驼峰(camelCase)
- 使用JSX语法时需要传入一个函数作为事件处理函数
在上面的例子中实现了将数据存储在state状态中,并且可以从状态中删除任何数据。下面通过事件处理方式说明如何添加数据。
首先创建一个表单Form.js,将表单的初始状态设置为带有空属性的对象,并且将初始状态赋给this.state:
import React, { Component } from 'react'
class Form extends Component {
initialState = {
name: '',
job: '',
}
state = this.initialState
handleChange = (event) => {
const { name, value } = event.target;
this.setState({
[name]: value,
});
}
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>
);
}
}
export default Form;
通过onChange事件监听输入框的变化,将handleChange方法绑定到该事件上。一旦有输入,则触发执行handleChange方法,在该方法中由setState实时更新输入框的内容。
注意:在class组件中,class的方法默认不会绑定this,所以要谨慎JSX回调函数中的this。
例如上面的例子中,如果忘记绑定this,直接将this.handleChange传入onChange,调用函数时this将会是undefined。
绑定this的方式:
(1)class的方法上使用箭头函数,如上例所示,handleChange = (event) => {…} 。
(2)显式绑定:
// 直接在传入时进行绑定
onChange={this.handleChange.bind(this)}
接下来提交表单,在输入框后面增加一个提交按钮,并将onClick事件与submitForm函数进行绑定:
// 提交表单
submitForm = () => {
this.props.handleSubmit(this.state);
// 添加后清空state:
this.setState(this.initialState);
}
render() {
...
return (
<form>
...
<input type = "button" value="submit" onClick={this.submitForm} />
</form>
);
}
当点击提交按钮时触发subForm函数,该函数执行了从父组件app.js传递过来的handleSubmit函数:
handleSubmit = (character) => {
// 添加数据
this.setState({
characters: [...this.state.characters, character]
})
}
render() {
return (
<div className="app">
<h1>Hello, React</h1>
<Table
characterData={this.state.characters}
removeCharacter={this.removeCharacter}>
</Table>
<Form handleSubmit={this.handleSubmit} />
</div>
);
}
handleSubmit函数将输入的数据添加到原来的表单之中,就达到了添加数据的效果。
条件渲染
React中的条件渲染和javascript中一样,可以使用运算符if或者条件运算。
下面实现一个登陆退出按钮,根据不同的状态实现不同的效果。为了方便,还是在上面的项目基础上实现。
添加一个Login.js文件:
import React, { Component } from 'react'
// 登录
const LoginButton = (props) => {
return <button onClick={props.click}>Login</button>;
}
// 注销
const LogoutButton = (props) => {
return <button onClick={props.click}>Logout</button>
}
// 登录描述
const LoginGreeting = () => {
return <span>Welcome </span>;
};
// 注销描述
const LogoutGreeting = () => {
return <span>Please sign up</span>;
};
const Greeting = (props) => {
const {isLoggedIn} = props;
return <div>{isLoggedIn ? <LoginGreeting/> : <LogoutGreeting/>}</div>
}
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 = <LogoutButton click={this.handleLogoutClick}></LogoutButton>
} else {
button = <LoginButton click={this.handleLoginClick}></LoginButton>
}
return (
<div>
<Greeting isLoggedIn = {isLoggedIn} />
{button}
</div>
);
}
}
export default LoginControl
然后修改app.js,在表单后添加登录按钮和greeting:
<LoginControl />
就可以实现按照是否登录条件来显示登录按钮状态了:
列表和key
在上面的例子中TableBody组件中使用了tr
和td
元素,可以看到,每个tr
中都有一个特殊的属性key
,这个key
是必须的。
key
帮助React识别哪些元素发生了改变,比如删除或添加。key
就可以作为这个改变的元素的唯一标识,因此key
在列表中应该是独一无二的。通常使用id来作为元素的key
,如果元素没有确定的id
时,可以使用元素索引index
作为key
。