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
| this.state.comment = 'Hello';
|
使用 this.setState() 方法来改变它的值
1 2
| this.setState({comment: 'Hello'});
|
(2) this.state 的更新可能是异步的(this.props 也是如此)
React 可能会批量地调用 this.setState() 方法,this.state 和 this.props 也可能会异步地更新,所以你不能依赖它们目前的值去计算它们下一个状态。
比如下面更新计数器的方法会失败:
1 2 3 4
| 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 阻止浏览器默认行为的方式最简单,用一个装饰符就可以搞定
。
而在 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> ); }
|