React 提倡组件化的开发方式,每个组件只关心自己部分的逻辑,使得应用更加容易维护和复用。
React 还有一个很大的优势是基于组件的状态更新视图,对于测试非常友好。
数据模型
state
React 每一个组件的实质是状态机(State Machines),在 React 的每一个组件里,通过更新 this.state,再调用 render() 方法进行渲染,React 会自动把最新的状态渲染到网页上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class HelloMessage extends React.Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
this.state = {enable: false};
}
handleClick() {
this.setState({enable: !this.state.enable})
}
render() {
return (
<div>
<input type="text" disabled={this.state.enable} />
<button onClick={this.handleClick}>click this</button>
</div>
);
}
}
ReactDOM.render(
<HelloMessage />,
document.getElementById('root')
);
通过在组件的 constructor 中给 this.state 赋值,来设置 state 的初始值,每当 state 的值发生变化, React 重新渲染页面。
注意:
(1) 请不要直接编辑 this.state,因为这样会导致页面不重新渲染
1
2
// Wrong
this.state.comment = 'Hello';
使用 this.setState() 方法来改变它的值
1
2
// Correct
this.setState({comment: 'Hello'});
(2) this.state 的更新可能是异步的(this.props 也是如此)
React 可能会批量地调用 this.setState() 方法,this.state 和 this.props 也可能会异步地更新,所以你不能依赖它们目前的值去计算它们下一个状态。
比如下面更新计数器的方法会失败:
1
2
3
4
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
第二种形式的 setState() 方法接收的参数为一个函数而不是一个对象。函数的第一个参数为 previous state,第二个参数为当前的 props
1
2
3
4
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
实现一个计数器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class HelloMessage extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {counter: 0};
}
handleClick() {
this.setState((prevState, props) => ({
counter: prevState.counter + parseInt(props.increment)
}));
}
render() {
return (
<div>
<h1>{this.state.counter}</h1>
<button onClick={this.handleClick}>click this</button>
</div>
);
}
}
ReactDOM.render(
<HelloMessage increment="1" />,
document.getElementById('root')
);
props
React 的数据流是单向的,是自上向下的层级传递的,props 可以对固定的数据进行传递。
1
2
3
4
5
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
state vs props
state 和 props 看起来很相似,其实是完全不同的东西。
一般来说,this.props 表示那些一旦定义,就不再改变的特性,比如购物车里的商品名称、价格,而 this.state 是会随着用户互动而产生变化的特性,比如用户购买商品的个数。
获取 DOM
在 React 中,我们可以通过 this.refs 方便地获取 DOM:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class HelloMessage extends React.Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
alert(this.refs.myInput.value);
}
render() {
return (
<div>
<input ref="myInput" />
<button onClick={this.handleClick}>click this</button>
</div>
);
}
}
ReactDOM.render(
<HelloMessage />,
document.getElementById('root')
);
生命周期
React 组件的生命周期分为三类:
1. 挂载(Mounting): 已插入真实 DOM
- componentWillMount(): 在初次渲染之前执行一次,最早的执行点
- componentDidMount(): 在初次渲染之后执行
getInitialState() –> componentWillMount() –> render() –> componentDidMount()
2. 更新(Updating): 正在被重新渲染
- componentWillReceiveProps(): 在组件接收到新的 props 的时候调用。在初始化渲染的时候,该方法不会调用。
- shouldComponentUpdate(): 在接收到新的 props 或者 state,将要渲染之前调用。
- componentWillUpdate(): 在接收到新的 props 或者 state 之前立刻调用。
- componentDidUpdate(): 在组件的更新已经同步到 DOM 中之后立刻被调用。
componentWillReceiveProps() –> shouldComponentUpdate() –> componentWillUpdate –> render() –> componentDidUpdate()
3. 移除(Unmounting): 已移出真实 DOM
- componentWillUnmount(): 在组件从 DOM 中移除的时候立刻被调用。
下面举 React 官网的一个输出时间的例子,在 Clock 渲染之前设置一个定时器,每隔一秒更新一下 this.state.date 的值,并在组件移除的时候清除定时器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
//组件初次渲染之后执行
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
//组件移除的时候执行
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
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')
);
事件
React 内建的跨浏览器的事件系统,我们可以在组件里添加属性来绑定事件和相应的处理函数。这种事件绑定方法极大的方便了事件操作,不用再像以前先定位到 DOM 节点,再通过 addEventListener 绑定事件,还要用 removeEventListener 解绑。当组件注销时,React 会自动帮我们解绑事件。
React 处理事件与 DOM 处理事件非常相似,有以下两点不同:
- React 事件用驼峰命名法,而不是全小写
- 通过 JSX 语法传递函数作为事件处理器,而不是字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
class LoggingButton extends React.Component {
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
另外一个不同的是 React 不支持向事件处理函数 return false,一般 HTML 事件函数中,可以通过 return false 来阻止默认行为,比如
1
2
3
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
</a>
Vue 阻止浏览器默认行为的方式最简单,用一个装饰符就可以搞定 <form v-on:submit.prevent="onSubmit"></form>。
而在 React 中,必须调用 preventDefault 方法才能完成以上功能。
1
2
3
4
5
6
7
8
9
10
11
12
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
在这里的 e 是 React 封装过后的,因此不用担心游览器差异带来的影响。☺
条件渲染
假设 Greeting 组件根据状态选择渲染 UserGreeting 和 GuestGreeting 中的一个。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}
handleLogoutClick() {
this.setState({isLoggedIn: !this.state.isLoggedIn});
}
render() {
const isLoggedIn = this.state.isLoggedIn;
let button = null;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLogoutClick} />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
}
行内条件判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById('root')
);
其它类型的逻辑判断,像三元运算符,if else React 也均支持。
1
2
3
4
5
6
7
8
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
</div>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
);
}
阻止组件渲染 通过在组件内部 return null 可以达到阻止组件渲染的
1
2
3
4
5
6
7
8
9
10
11
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return (
<div className="warning">
Warning!
</div>
);
}