init
ReactDOM.render(
<h1>hello, world!</h1>
document.getElementById('root')
)
JSX
JSX
是Javascript
的语法扩展,建议在React
中配合使用JSX
,可以描述UI
交互,并且具有Javascript
的全部功能。JSX
中放置的大括号{}
会被当做有效的Javascript
表达式执行。Babel
会将JSX
转译成 React.createElement
函数调用。以下两种示例代码完全等效
cont element = (
<h1 className="greeting">
Hello. word
</h1>
)
const element = React.createElement(
'h1',
{className: 'greeting'},
'hello. world'
)
React.createElement
函数会预先执行一些检查,以帮助编写无错代码,实际上创建了一个被称为 React
元素的对象,React
通过读取这些对象,使用他们来构建DOM
并保持数据更新。
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'hello. world'
}
}
元素渲染
React
元素是不可变对象,被创建后无法更改它的子元素或属性。更新UI
的方式是创建一个全新的元素,将其传入ReactDOM.render()
。React DOM
会将元素和它的子元素间的状态进行比较,只会进行必要的更新使得DOM
达到预期的状态。
function tick() {
const element = (
<div>
<h1>hello. world</h1>
<h2>It is { new Date().toLocaleTimeString() }<h2>
</div>
)
ReactDOM.render(element, document.getElementBtId('root'))
}
setInterval(tick, 1000)
组件 & Props
组件允许将 UI 拆分为独立可复用的代码片段,接受任意的入参 props
,返回用于展示页面元素内容的 React
元素。
函数组件接收 props
对象,返回 React
元素。
function Welcome(props) {
return <h1>hello {props.name}</h1>
}
也可以使用 ES6
的 class
定义组件
class Welcome extends React.Component {
render() {
return <h1>hello {this.props.name}</h1>
}
}
渲染组件
React
元素为用户自定义组件时,会将 JSX
接收的属性和子组件转换为单个props
对象传递给组件。下面的部分会被渲染成 Hello Sara
。
function Welcome(props) {
return <h1>hello . {props.name}</h1>
}
const element = <Welcome name="Sara"/>
ReactDOM.render(
element,
document.getElementById('root')
)
组件名称必须以大写字母开头,React
会将以小写字母开头的组件视为原生 DOM
标签。这个例子中发生的流程
调用
ReactDOM.render()
函数,并传入<Welcome name="Sara"/>
作为参数React
调用Welcome
组件,并将{name: 'Sara'}
作为props
传入Welcome
组件将<h1>Hello, Sara</h1>
元素作为返回值React DOM
将DOM
高效更新为<h1>Hello, Sara<h1>
每个新的React
应用程序的顶层组件都是App
组件,如果将React
集成到现有的应用程序中,需要使用一些小的功能性组件,自下而上地将这类组件逐步应用到视图中的每一处。
function Welcome(props) {
return <h1>hello . {props.name}</h1>
}
function App() {
return (
<div>
<Welcome name="Sara"/>
<Welcome name="Cahal"/>
<Welcome name="Edite"/>
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
所有 React
组件必须像纯函数(不会更改函数入参)一样保护 props
不被更改。state
允许React
组件随用户操作、网络相应或者其他变化而动态更改输出内容。
State 与生命周期
state
与props
类似,props
是私有的,并且完全受控于当前组件。将函数组件转换成 class
组件:
class Clock extends React.Component {
constructor(props) {
super(props) //将props传递到父类的构造函数中, class组件应该始终使用props参数来调用父类的构造函数
this.state = {date: new Date()}
}
componentDidMount() {//componentDidMount方法会在组件已经被渲染到DOM中后运行
this.timerId = setInterval(() => this.tick(), 100)
}
componentWillUnmount() {
clearInterval(this.timerId)
}
tick() {
this.setState({ //使用 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')
)
每次组件更新时render
方法都会被调用,但只要在相同的DOM
节点中渲染<Clock/>
,就仅有一个Clock
组件的class
实例被创建使用,使得我们可以使用state
或生命周期方法的很多特性。
关于state
使用的注意事项
-
构造函数是唯一可以给
this.state
赋值的地方,其他情况下不要直接修改state
,要通过setState()
this.setState({comment: 'hello'})
-
state
的更新可能是异步的。出于性能考虑,React
可能会把多个setState()
调用合并成一个调用。this.props
和this.state
可能是异步更新的,不要依赖他们的值更新下一个状态。解决方式是让setState
接收一个函数而不是对象。this.setState((state, props) => ({ m: state.a + props.b }))
当调用
setState()
时,React
会把提供的对象合并到当前的state
中-
数据是向下流动的,除了拥有并设置
state
的组件,其他组件无法访问。组件可以选择把它的state
作为props
向下传递到它的子组件中。function FormattedDate(props) { return <h2>It is { props.date.toLocaleTimeString() }</h2> } <FormattedDate date={this.state.date}></FormattedDate>
事件处理
React
元素的事件处理和DOM
元素的很相似,但有几点不同
React
的事件命名采用小驼峰式,不是纯小写-
使用
JSX
语法时需要传入一个函数作为事件处理函数,不是字符串<button onClick={activateLasers}> Active Lasers</button>
-
不能通过返回
false
的形式阻止事件的默认行为,必须使用显示的preventDefault
function ActionLink() { function handleClick(e) { e.preventDefault() // e是一个合成事件,react 根据 w3c 规范来定义这些合成事件,不需要担心跨浏览器的兼容问题 console.log('The link was clicked') } return ( <a href="#" onClick={handleClick}>Click me</a> ) }
-
在
React
中,不需要使用addEventListener
为已创建的DOM
元素添加监听器,只需要在元素初始渲染的时候添加监听器即可// 渲染一个让用户切换开关状态的按钮 class Toggle extends React.Component { constructor(props) { super(props) this.state = { isToggleOn: true } this.handleClick = this.handleClick.bind(this) // 为了在回调中绑定 this,这个绑定是必不可少的 } handleClick() { this.setState(state => ({ isToggleOn: !state.isToggleOn })) } render() { return ( <button onClick={this.handleClick}> {this.state.isToggleOn ? 'ON' : 'OFF'} </button> ) } }
-
JSX
函数中的this
需要正常使用的情况下,需要使用bind
进行绑定class Toggle extends React.Component { constructor(props) { super(props) this.state = { isToggleOn: true} this.handleClick = this.handleClick.bind(this) //为了在回调中使用this } handleClick() { this.setState(state => ({ isToggleOn: !state.isToggleOn })) } // class Fields 语法 handleClick = () => { console.log('this is:', this) } render() { return ( <button onClick={this.handleClick}> <!--> 或者在回调中使用箭头函数 <button onClick={() => this.handleClick()}> <--> {this.state.isToggleOn ? 'ON' : 'OFF'} </button> ) } } ReactDOM.render( <Toggle/>, document.getElementById('root') )
-
事件的参数传递
<button onClick={(e) => this.deleteRow(id, e)}>Detelet Row</button> <button onClick={this.deleteRow(this, id)}>Detelet Row</button>
条件渲染
-
if
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/> } ReactDOM.render( <Greeting isLoggedIn={false}/>, document.getElementById('root') )
-
与 &&
function Mailbox(props) { const unreadMessage = props.unreadMessage return ( <div> <h1>hello</h1> {unreadMessage.length > 0 && <h2>You hava {unreadMessage.length} unread message/</h2> } </div> ) } const message = ['a', 'b', 'c'] ReactDOM.render( <Mailbox unreadMessages={message}/>, document.getElementById('root') )
-
三目 ? :
render() { const isLoggedIn = this.state.isLoggedIn return ( <div> The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in. </div> ) }
-
阻止条件渲染
在组件已经被其他组件渲染的情况下,隐藏组件,设置在组件的
render
方法中返回null
function WarningBanner(props) { if(!props.warn) return null return ( <div classNmae='warning'>Warning</div> ) }
列表 & key
-
列表渲染
function NumberList(props) { const numbers = props.numbers const listItems = numbers.map((number) => <li key={number.toString()}> {number} </li> ) return ( <ul>{listItems}</ul> ) } const numbers = [1, 2, 3, 4, 5] ReactDOM.render( <NumberList number={number}/>, document.getElementById('root') )
key
帮助React
识别元素的添加和删除,应该给数组中的每一个元赋予一个确定的标识。一个元素的key
最好是这个元素在列表中独一无二的字符串,通常使用数据的id
作为元素的key
。如果选择不显示指定key
值,react
将默认使用索引作为列表项目的key
值。key
只需要在兄弟节点之间唯一,不需要全局唯一。在map
方法中的元素需要设置key
属性。
function ListItem(props) {
return <li>{props.value}</li>
}
function NumberList(props) {
const numbers = props.numbers
const listItens = numbers.map((number) =>
<ListItem key={number.toString()} value={number}/>
)
return (
<ul>
{listItems}
</ul>
)
}
const numbers = [1, 2, 3, 4, 5]
ReactDOM.render(
<NumberList number={numbers}/>,
document.getElementById('root')
)
ket
会传递信息给React
,但不会传递给组件。如果组件中需要使用key
属性的值,请用其他属性名显示传递这个值。
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
)
// Post组件可以读出 props.id,不能读出 props.key
表单
在html
中,表单元素 <input>
,<textarea>
和<select>
通常自己维护state
,并且根据用户输入进行更新。在React
中,可变状态 mutable state
通常保存在组件的state
属性中,只能通过setState
更新。React
的state
作为作为表单元素的唯一数据源,渲染表单的React
组件控制着用户输入过程中表单发生的操作,这种表单元素叫做受控组件。
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 (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type='text' value={this.state.value} onChange={this.handleChange}></input>
</label>
<input type='submit' value='提交'/>
</form>
)
}
}
由于在表单元素上设置了value
属性,因此显示的值将始终为this.state.value
,使得React
的state
成为唯一数据源。由于handleChange
在每次更新时都会执行并更新React
的state
,显示的值将随着用户输入而更新。
一些需要注意的处理场景:
-
文件
input
标签。在HTML
中,<input type='file'>
允许用户从存储设备中选择一个或多个文件,将其上传到服务器,此时为受控组件。<input type='file'> <!--value只读,是React中的一个非受控组件-->
-
受控组件输入空值
ReactDOM.render(<input value='hi'/>, mountNode) setTimeout(function(){ ReactDOM.render(<input value={null}/>, mountNode) }, 1000)
状态提升
多个组件需要反映相同的变化数据,将共享状态提升到最近的共同父组件中。实现一个输入的温度转换功能(摄氏度与华氏度)
class Temperature extends React.Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
}
handleChange(e) {
this.props.onTemperatureChange(e.target.value)
// 移除组件自身的 state,通过获取 props 获取温度数据,当想要响应数据改变时,需要调用 父组件提供的 this.props.onTemperatureChange(), 不再使用 this.setState()
}
render() {
const temperature = this.props.temperature
const scale = this.props.scale
return (
<fieldset>
<legend>Enter temperature in {scaleNmaes[scale]}</legend>
<input value={temperature}
onChange={this.handleChange}/>
</fieldset>
)
}
}
针对整体的Calculator
组件
class Calculator extends React.Component {
constructor(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.steState({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}/>
/>
</div>
)
}
}
在React
中,任何可变数据应当只有一个唯一数据源,state是首先添加到需要渲染数组的组件中,如果其他组件也需要这个state
,可以将他提升到这些组件的最近共同父组件中,依靠自上而下的数据流,而不是尝试在不同组件间同步state
。提升state
的方式比双向绑定方式需要编写更多代码,但是可以使得排查和隔离bug
所需的工作量变小。
组合 & 继承
-
使用
children prop
将子组件传递到渲染结果中function SplitPane(props) { return ( <div className='SplitPane'> <div className='SplitPane-left'> {pros.left} </div> <div className='SplitPane-right'> {pros.right} </div> </div> ) } function App() { return ( <SplitPane left={ <Contacts/> }/> <SplitPane right={ <Chat/> }/> ) }
在
FaceBook
的各个应用场景下,没有发现需要使用继承来构建组件层次的情况。props
和组合提供了定制组件的灵活方式,组件可以接受任意props
,包括基本数据类型,React
元素及函数。如果想在组件间复用非UI的功能,可以将其提取为单独的javascript
模块,组件可以直接import
,无需通过extend
继承。