1、(确保安装node.js>=8.10)
npx create-react-app my-app
cd my-app
npm start
2、删除掉新项目中 src/ 文件夹下的所有文件(不要删除src)
cd my-app
cd src
# 如果你使用 Mac 或 Linux:
rm -f *
# 如果你使用 Windows:
del *
# 然后回到项目文件夹
cd ..
在src下创建一个index.js,添加如下代码:
import React from 'react'
import ReactDom from 'react-dom/client'
const root = ReactDom.createRoot(document.getElementById("root"));
root.render(<h1>Hello World</h1>);
在终端下输入:npm start,就可以看见网页中的HelloWorld了。
JSX,是一个 JavaScript 的语法扩展。我们建议在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模板语言,但它具有 JavaScript 的全部功能。
const element = <h1>Hello, world!</h1>;
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
在 JSX 语法中,你可以在大括号内放置任何有效的JavaScript表达式。
在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;}
你可以通过使用引号,来将属性值指定为字符串字面量:
const element = <a href="https://www.reactjs.org"> link </a>;
也可以使用大括号,来在属性值中插入一个 JavaScript 表达式:
const element = <img src={user.avatarUrl}></img>;
** 警告:**
因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。
例如,JSX 里的 class 变成了 className,而 `tabindex` 则变为 tabIndex 。
假如一个标签里面没有内容,你可以使用 /> 来闭合标签,就像 XML 语法一样:
const element = <img src={user.avatarUrl} />;
JSX 标签里能够包含很多子元素:
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
你可以安全地在 JSX 当中插入用户输入内容
const title = response.potentiallyMaliciousInput;
// 直接使用是安全的:
const element = <h1>{title}</h1>;
React DOM 在渲染所有输入内容之前,默认会进行转义。它可以确保在你的应用中,永远不会注入那些并非自己明确编写的内容。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止XSS攻击。
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
元素是构成 React 应用的最小砖块。
想要将一个 React 元素渲染到根 DOM 节点中,只需把它们一起传入ReactDOM.createRoot():
const root = ReactDOM.createRoot(
document.getElementById('root')
);
const element = <h1>Hello, world</h1>;
root.render(element);
React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。一个元素就像电影的单帧:它代表了某个特定时刻的 UI。
根据我们已有的知识,更新 UI 唯一的方式是创建一个全新的元素,并将其传入 root.render()。
考虑一个计时器的例子:
const root = ReactDOM.createRoot(
document.getElementById('root')
);
function tick() {
const element = (
Hello, world!
It is {new Date().toLocaleTimeString()}.
);
root.render(element);}
setInterval(tick, 1000);
组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。
定义组件最简单的方式就是编写 JavaScript 函数:
function Welcome(props) {
return Hello, {props.name}
;
}
你同时还可以使用 ES6 的 class 来定义组件:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
上述两个组件在 React 里是等效的。
当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const root = ReactDOM.createRoot(document.getElementById('root'));
const element = <Welcome name="Sara" />;
root.render(element);
让我们来回顾一下这个例子中发生了什么:
root.render() 函数,并传入 作为参数。Welcome 组件,并将 {name: 'Sara'} 作为 props 传入。Welcome 组件将 Hello, Sara
元素作为返回值。Hello, Sara
。注意: 组件名称必须以大写字母开头。
React 会将以小写字母开头的组件视为原生 DOM 标签。
组件可以在其输出中引用其他组件。这就可以让我们用同一组件来抽象出任意层次的细节。按钮,表单,对话框,甚至整个屏幕的内容:在 React 应用程序中,这些通常都会以组件的形式表示。
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
将组件拆分为更小的组件。
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
首先,我们将提取 Avatar 组件:
function Avatar(props) {
return (
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/>
);
}
我们现在针对 Comment 做些微小调整:
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<Avatar user={props.author} />
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
接下来,我们将提取 UserInfo 组件,该组件在用户名旁渲染 Avatar 组件:
function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">
{props.user.name}
</div>
</div>
);
}
进一步简化 Comment 组件:
function Comment(props) {
return (
<div className="Comment">
<UserInfo user={props.author} />
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
组件无论是使用函数声明还是通过 class 声明,都绝不能修改自身的 props。
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
在不违反上述规则的情况下,state 允许 React 组件随用户操作、网络响应或者其他变化而动态更改输出内容。
State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。
关于 setState() 你应该了解三件事:
1、不要直接修改 State,而是应该使用 setState():
2、State 的更新可能是异步的
出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。
因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
3、State 的更新会被合并
当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state。
不管是父组件或是子组件都无法知道某个组件是有状态的还是无状态的,并且它们也并不关心它是函数组件还是 class 组件。
这就是为什么称 state 为局部的或是封装的的原因。除了拥有并设置了它的组件,其他组件都无法访问。
组件可以选择把它的 state 作为 props 向下传递到它的子组件中
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
<button onClick={activateLasers}>
Activate Lasers
</button>
在 React 中另一个不同点是你不能通过返回 false 的方式阻止默认行为。你必须显式地使用 preventDefault。例如,在 React 中,可能是这样的:
function Form() {
function handleSubmit(e) {
e.preventDefault();
console.log('You clicked submit.');
}
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
使用 React 时,你一般不需要使用 addEventListener 为已创建的 DOM 元素添加监听器。事实上,你只需要在该元素初始渲染的时候添加监听器即可。
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
如果觉得使用 bind 很麻烦,这里有两种方式可以解决。你可以使用 public class fields 语法 :
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick.
handleClick = () => {
console.log('this is:', this);
};
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
如果你没有使用 class fields 语法,你可以在回调中使用箭头函数:
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// 此语法确保 `handleClick` 内的 `this` 已被绑定。
return (
<button onClick={() => this.handleClick()}>
Click me
</button>
);
}
}
在循环中,通常我们会为事件处理函数传递额外的参数。例如,若 id 是你要删除那一行的 ID,以下两种方式都可以向事件处理函数传递参数:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。
在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后,依据应用的不同状态,你可以只渲染对应状态下的部分内容。
观察这两个组件:
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
再创建一个 Greeting 组件,它会根据用户是否登录来决定显示上面的哪一个组件。
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
const root = ReactDOM.createRoot(document.getElementById('root'));
// Try changing to isLoggedIn={true}:
root.render(<Greeting isLoggedIn={false} />);
你可以使用变量来储存元素。 它可以帮助你有条件地渲染组件的一部分,而其他的渲染部分并不会因此而改变。
通过花括号包裹代码,你可以在 JSX 中嵌入表达式。这也包括 JavaScript 中的逻辑与 (&&) 运算符。它可以很方便地进行元素的条件渲染:
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
Hello!
{unreadMessages.length > 0 &&
You have {unreadMessages.length} unread messages.
}
);
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render( );
另一种内联条件渲染的方法是使用 JavaScript 中的三目运算符 condition ? true : false。
在极少数情况下,你可能希望能隐藏组件,即使它已经被其他组件渲染。若要完成此操作,你可以让 render 方法直接返回 null,而不进行任何渲染。
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return (
Warning!
);
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {showWarning: true};
this.handleToggleClick = this.handleToggleClick.bind(this);
}
handleToggleClick() {
this.setState(state => ({
showWarning: !state.showWarning
}));
}
render() {
return (
);
}
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render( );
你可以通过使用 {} 在 JSX 内构建一个元素集合。
下面,我们使用 Javascript 中的 map() 方法来遍历 numbers 数组。将数组中的每个元素变成 标签,最后我们将得到的数组赋值给 listItems:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) => - {number}
);
然后,我们可以将整个 listItems 插入到 元素中:
{listItems}
我们可以把前面的例子重构成一个组件,这个组件接收 numbers 数组作为参数并输出一个元素列表。
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
- {number}
);
return (
{listItems}
);
}
const numbers = [1, 2, 3, 4, 5];
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render( );
当我们运行这段代码,将会看到一个警告 a key should be provided for list items,意思是当你创建一个元素时,必须包括一个特殊的 key 属性。
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
-
{number}
);
return (
{listItems}
);
}
key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
-
{number}
);
一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的 id 来作为元素的 key:
const todoItems = todos.map((todo) =>
-
{todo.text}
);
当元素没有确定 id 的时候,万不得已你可以使用元素索引 index 作为 key:
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
-
{todo.text}
);
元素的 key 只有放在就近的数组上下文中才有意义。
比方说,如果你提取出一个 ListItem 组件,你应该把 key 保留在数组中的这个 元素上,而不是放在 ListItem 组件中的 元素上。
例子:不正确的使用 key 的方式
function ListItem(props) {
const value = props.value;
return (
// 错误!你不需要在这里指定 key:
-
{value}
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 错误!元素的 key 应该在这里指定:
);
return (
{listItems}
);
}
例子:正确的使用 key 的方式
function ListItem(props) {
// 正确!这里不需要指定 key:
return - {props.value}
;}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 正确!key 应该在数组的上下文中被指定
);
return (
{listItems}
);
}
在 React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state。例如这个纯 HTML 表单只接受一个名称:
在 HTML 中,表单元素(如、 和 )通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。
例如,如果我们想让前一个示例在提交时打印出名称,我们可以将表单写为受控组件:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('提交的名字: ' + this.state.value);
event.preventDefault();
}
render() {
return (
);
}
}
在 React 中, 使用 value 属性代替。这样,可以使得使用 的表单和使用单行 input 的表单非常类似:
class EssayForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '请撰写一篇关于你喜欢的 DOM 元素的文章.'
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('提交的文章: ' + this.state.value);
event.preventDefault();
}
render() {
return (
);
}
}
React 并不会使用 selected 属性,而是在根 select 标签上使用 value 属性。这在受控组件中更便捷,因为您只需要在根标签中更新它。例如:
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('你喜欢的风味是: ' + this.state.value);
event.preventDefault();
}
render() {
return (
);
}
}
当需要处理多个 input 元素时,我们可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作。
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value });
}
render() {
return (
);
}
}
这里使用了 ES6 计算属性名称的语法更新给定输入名称对应的 state 值:
例如:
this.setState({
[name]: value
});
等同 ES5:
var partialState = {};
partialState[name] = value;
this.setState(partialState);
通常,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。让我们看看它是如何运作的。
官网的例子可能不太好懂,我写了个简单版的例子:
首先,创建输入框组件和App组件:
class Input extends React.Component {
constructor(props) {
super(props);
this.state = {
name: ""
}
this.change = this.change.bind(this)
}
change(e) {
this.setState({
value: e.target.value,
})
}
render() {
const name = this.props.name;
const value = this.state.value;
const change = this.change;
return (
)
}
}
class App extends React.Component {
render() {
return (
)
}
}
此时,输入框是各自管各自的,现在的需求是这样,当在第一个输入框输入一个数时,第二个输入框,显示这个数的三倍;同样,在第二个输入框输入一个数时,第一个输入框显示这个数的两倍。
此时就需要实现数据共享,让两个Input组件访问到同一个数据,再进行转换。所以我们把这个数据,提升到父组件的state中去。
class Input extends React.Component {
constructor(props) {
super(props);
this.state = {
name: ""
}
this.change = this.change.bind(this)
}
change(e) {
this.props.change(e.target.value);
}
render() {
const name = this.props.name;
const value = this.props.value;
const change = this.change;
return (
)
}
}
class App extends React.Component {
constructor(props){
super(props);
this.change = this.change.bind(this)
}
change(e){
this.setState({
value: e,
})
}
render() {
const value = this.state.value;
const change = this.change;
return (
)
}
}
此时,已经实现了数据同步,当第一个输入框输入数据时,第二个输入框的数据会进行同步,接下来,进行数据的转换,编写转换函数:
function methodA(num){
return parseInt(num) * 2;
}
function methodB(num){
return parseInt(num) * 3;
}
转换函数编写好之后,进行代码修改,使数据能够进行根据名称转换:
class Input extends React.Component {
constructor(props) {
super(props);
this.change = this.change.bind(this)
}
change(e) {
this.props.change(e.target.value);
}
render() {
const name = this.props.name;
const value = this.props.value;
const change = this.change;
return (
)
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'A',
value: 0
}
this.changeA = this.changeA.bind(this);
this.changeB = this.changeB.bind(this);
}
changeA(e) {
this.setState({
name: 'A',
value: e,
})
}
changeB(e){
this.setState({
name: 'B',
value: e,
})
}
render() {
const value = this.state.value;
const name = this.state.name;
const valueA = name === 'A' ? value : methodA(value);
const valueB = name === 'B' ? value : methodB(value);
return (
)
}
}
可能这个例子举的不是特别好,但勉强还是能理解状态提升是怎么一个情况了。