跳到主要内容

React入门教程

1、JSX 简介

const element = <h1>Hello, world!</h1>;

等号后面的语法,不是Javascript的语法,但是在React是合理的,这就是JSX语法。

所有HTML中的标签都可以在JSX中表示为一个元素,而且在元素中可以嵌套任何变量,比如:

const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;

JSX 表达式本身也是一个变量,可以赋值给变量,作为函数参数和返回值传递。

2、元素渲染

元素描述了你在屏幕上想看到的内容。

想要将一个 React 元素渲染到根 DOM 节点中,只需把它们一起传入ReactDOM.render()

const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));

React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。更新 UI 唯一的方式是创建一个全新的元素,并将其传入ReactDOM.render()

function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(element, document.getElementById('root'));
}

setInterval(tick, 1000);

尽管每一秒我们都会新建一个描述整个 UI 树的元素,React DOM 只会更新实际改变了的内容。

3、组件 & Props

组件是可复用的代码片段,每个代码片段包括元素(实际展示的内容)和行为。

函数组件和class组件:

function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

下面是一个自定义组件的例子:

const element = <Welcome name="Sara" />;

当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。

组件名称必须以大写字母开头。React 会将以小写字母开头的组件视为原生 DOM 标签。

函数不会尝试更改入参,且多次调用下相同的入参始终返回相同的结果,这就是纯函数,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

4、State & 生命周期

  1. 每次在组件中调用setState时,React 都会自动更新其子组件。
  2. state 对于每个组件来说是私有的

我们将第二节里的Clock组件做成一个Class组件:

class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}

render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}

ReactDOM.render(
<Clock />,
document.getElementById('root')
);

接下来还要为Clock绑定一个定时器:当 Clock 组件第一次被渲染到 DOM 中的时候,就为其设置一个计时器。这在 React 中被称为“挂载(mount)”。

同时,当 DOM 中 Clock 组件被删除的时候,应该清除计时器。这在 React 中被称为“卸载(unmount)”。

通过加上下面的方法:

  componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}

componentWillUnmount() {
clearInterval(this.timerID);
}

tick() {
this.setState({
date: new Date()
});
}

5、事件处理

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
function ActionLink() {
// e 是一个合成事件
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}

return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}

在 JavaScript 中,class 的方法默认不会绑定this。如果你忘记绑定this.handleClick并把它传入了onClick,当你调用这个函数的时候this的值为undefined

所以要在class的构造函数中,显示地绑定:

// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this);

在 React 中,有一个命名规范,通常会将代表事件的监听 prop 命名为on[Event],将处理事件的监听方法命名为handle[Event]这样的格式。

6、条件渲染

6.1 与运算符 &&

function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}

如果条件是 true&& 右侧的元素就会被渲染,如果是 false,React 会忽略并跳过它。

6.2 三目运算符

render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn
? <LogoutButton onClick={this.handleLogoutClick} />
: <LoginButton onClick={this.handleLoginClick} />
}
</div>
);
}

6.3 阻止组件渲染

在极少数情况下,你可能希望能隐藏组件,即使它已经被其他组件渲染。若要完成此操作,你可以让 render 方法直接返回 null,而不进行任何渲染。

7、列表 & Key

一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。一个好的经验法则是:在 map() 方法中的元素需要设置 key 属性。

在使用列表创建元素的时候,每个元素必须要有一个key,数组元素中使用的 key 在其兄弟节点之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的 key 值。

key 会传递信息给 React ,但不会传递给你的组件。如果你的组件中需要使用key属性的值,请用其他属性名显式传递这个值:

const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
);

上面例子中,Post 组件可以读出 props.id,但是不能读出 props.key

8、表单

总的来说,这使得<input type="text">,<textarea><select>之类的标签都非常相似—它们都接受一个value属性,你可以使用它来实现受控组件。

比如在 HTML 中, <textarea> 元素通过其子元素定义其文本:

<textarea>   你好, 这是在 text area 里的文本 </textarea>

而在 React 中,<textarea>使用value属性代替。

Select放弃了selected属性,也选用了value属性,下面this.state.value是选中的tap:

<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">葡萄柚</option>
<option value="lime">酸橙</option>
<option value="coconut">椰子</option>
<option value="mango">芒果</option>
</select>

9、状态提升

多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。在 React 中,将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,便可实现共享 state。这就是所谓的“状态提升”。

下面的例子中,Calculator拥有两个TemperatureInput组件,需求是想让两个TemperatureInput组件可以保持同步,一个组件里面输入值,另一个组件里显示修改后的值,但是因为TemperatureInput组件自身保持了state,就没办法做到联动。

class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}

handleChange(e) {
this.setState({temperature: e.target.value});
}

render() {
const temperature = this.state.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}

class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
}
}

状态提升之后的代码如下所示:

class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}

handleChange(e) {
this.props.onTemperatureChange(e.target.value);
}

render() {
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}

class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
}

handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature});
}

handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature});
}

render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}

最主要的改动如下:对于TemperatureInput 将const temperature =this.stats.temperature;修改为了const temperature =this.props.temperature;,然后增加了onTemperatureChange属性用来将改动传递到父组件。

10、组合 vs 继承

React 有十分强大的组合模式。我们推荐使用组合而非继承来实现组件间的代码重用。

下面是一个包含关系,Contacts和Chat组件都是SplitPane的子组件,也是通过props进行传递。

function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}

function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}