react眾所周知的前端3大主流框架之一,由於出色的性能,完善的周邊設施風頭一時無兩。本文就帶大家一起掌握react。
jsx語法前端MVVM主流框架都有一套自己的模板處理方法,react則使用它獨特的jsx語法。在組件中插入html類似的語法,簡化創建view的流程。
下面讓我們來認識一下構建的兩種元素
原生元素ReactDOM.render((
<div>
<h1>標題</h1>
</div>
), document.getElementById('root'))
通過簡單的語法頁面就會被插入一個div+一個h1標籤。原生的html元素可以被直接使用。以上的語法並不是js支持的語法,需要被轉換之後才能運行。
自定義元素react強大之處就在於可以組件的自定義,實現組件的復用。如果我們創建了一個組件。我們也可以通過jsx語法調用。
import * as React from 'react'
class Page extends React.Component {
render() {
return (<div>
home111 © © \ua9
</div>)
}
}
ReactDOM.render((
<div>
<Page/>
</div>
), document.getElementById('root'))
我們定義了一個Page組件,可以在jsx裡面像調用html一樣直接調用。
插入動態數據let name = 'hi'
ReactDOM.render((
<div>
{name}
</div>
), document.getElementById('root'))
使用{}就可以插入數據,但是{}中間的必須是js表達式,不能是語句。如果表達式的執行結果是一個數組,則會自動join。
注釋jsx語法和html語法一樣,也是可以插入注釋,只不過寫的時候有一些區別
子組件注釋let name = 'hi'
ReactDOM.render((
<div>
{/* 注釋 */}
{name}
</div>
), document.getElementById('root'))
在子組件中插入注釋,需要使用{}包裹起來,在//之間插入注釋文字。
屬性注釋let name = 'hi'
ReactDOM.render((
<div>
{name}
<img /*
多行注釋
*/ src="1.jpg"/>
</div>
), document.getElementById('root'))
在標籤中間,可以插入一個多行注釋,類似上面的代碼。
屬性props可以向使用html的attr一樣使用屬性,就像下面img的src一樣
let name = 'hi'
ReactDOM.render((
<div>
<img src="1.png"/>
</div>
), document.getElementById('root'))
如果需要傳遞動態屬性,使用{},多個屬性,使用展開運算符
let props = {
src: '1.png',
alt: '1圖片'
}
ReactDOM.render((
<div>
<img src={"1.png"}/>
<img {...props}/>
</div>
), document.getElementById('root'))
兩個轉換,class-->className for-->htmlFor
因為class和for是javascript關鍵字,所以這裡需要用轉換之後名稱
ReactDOM.render((
<div className="tab">
<label htmlFor="name">姓名:</label><input id="name"/>
</div>
), document.getElementById('root'))
布爾屬性
如果一個屬性的值是布爾值,當這個值是true的時候則可以省略=後面的值,只保留key。
ReactDOM.render((
<div className="tab">
<input type="text" required/>
<input type="text" required={true}/>
</div>
), document.getElementById('root'))
原生元素的自定義屬性
react對元素屬性做了校驗,如果在原生屬性上使用此元素不支持的屬性,則不能編譯成功。必須使用data-前綴
ReactDOM.render((
<div className="tab">
<input type="text" data-init="22"/>
</div>
), document.getElementById('root'))
插入html如果動態的插入html元素,react出於安全性考慮會自動幫我們轉義。所以一定要動態的插入元素的話,使用dangerouslySetInnerHTML
ReactDOM.render((
<div className="tab">
<div dangerouslySetInnerHTML={{__html: '<span>test</span>'}}></div>
</div>
), document.getElementById('root'))
React組件創建React.createClass這是舊版本的api,使用React.createClass創建組件,配套的一些api,有getDefaultProps, getinitialstate。官方已經不建議使用了,使用下面新的api替代。
ES6 classesimport * as React from 'react'
class Page extends React.Component {
render() {
return (<div>
home
</div>)
}
}
這是一個實現了render方法的class。也是一個基本的react組件。
無狀態函數function Button(props, context) {
return (
<button>
<em>{props.text}</em>
<span>{context.name}</span>
</button>
);
}
純函數,不存在state,只接受props和state。純函數有優點,優點就是易於測試,無副作用。
React數據流statestate是組件的內部狀態,需要在視圖裡面用到的狀態,才需要放到state裡面去。如下,我們在類上創建一個state屬性,在視圖裡面通過使用this.state.name去引用。而這裡的state定義則代替的是getinitialstate方法。
import * as React from 'react'
class Page extends React.Component {
state = {
name: '小明'
}
render() {
return (<div>
{this}
</div>)
}
}
如何更新state呢,直接更改state其實可以可以的,不過這樣子無法觸發組件視圖的更新機制。所以使用 setState()api。值得注意的是setState是異步的,原因是react內部需要對setState做優化,不是state變了立刻去更新視圖,而是攔截一部分state的改變,等到合適的時機再去更新視圖。
import * as React from 'react'
class Page extends React.Component {
state = {
name: '小明'
}
render() {
setTimeout(() => this.setState({name: '小明兒子'}), 5000)
return (<div>
{this.state.name}
</div>)
}
}
真實開發中絕不要在render函數裡面去更改state,以上只是為了演示
propsprops是組件之間傳遞數據的最主要api, react推崇的是自頂向下的數據流向,也就是組件的數據要從父組件傳給子組件。如果子組件需要向父組件傳遞數據,則需要使用回調函數的方式。
import * as React from 'react'
class Child extends React.Component {
render() {
return (<div>
{this.props.parentName}
</div>)
}
}
class Parent extends React.Component {
state = {
name: '小明'
}
render() {
setTimeout(() => this.setState({name: '小明兒子'}), 5000)
return (<div>
<Child parentName={this.state.name}/>
</div>)
}
}
可以看到Child組件顯示了父組件的name。當父組件狀態更新了,子組件同步更新。那如何在子組件中更改父組件狀態呢?答案是回調函數。
import * as React from 'react'
class Child extends React.Component {
update() {
this.props.onChange('小明名字改了')
}
render() {
return (<div>
{this.props.parentName}
<button onClick={this.update.bind(this)}>更新</button>
</div>)
}
}
class Parent extends React.Component {
state = {
name: '小明'
}
changeName(name) {
this.setState({
name
})
}
render() {
setTimeout(() => this.setState({name: '小明兒子'}), 5000)
return (<div>
<Child onChange={this.changeName.bind(this)} parentName={this.state.name}/>
</div>)
}
}
注意哈:props是不可以更改的,這既不符合react單向數據流思想,也為維護帶來災難。
事件react裡面的用戶事件都是合成事件,被React封裝過。內部使用的還是事件的委託機制。 常用的事件有點擊事件onClick,input的onChange事件等,官網都可以查到。
合成事件的this指向問題就像上文一樣,我們綁定事件的方式很奇怪,使用了bind來顯示綁定this的指向。因為傳遞到組件內部的只是一個函數,而脫離了當前對象的函數的this指向是不能指到當前組件的,需要顯示指定。
通過bind<button onClick={this.update.bind(this)}>更新</button>
構造器內部指定import * as React from 'react'
class Child extends React.Component {
constructor(props) {
super(props)
this.update = this.update.bind(this)
}
update() {
this.props.onChange('小明名字改了')
}
render() {
return (<div>
{this.props.parentName}
<button onClick={this.update}>更新</button>
</div>)
}
}
箭頭函數import * as React from 'react'
class Child extends React.Component {
update => e = {
this.props.onChange('小明名字改了')
}
render() {
return (<div>
{this.props.parentName}
<button onClick={this.update}>更新</button>
</div>)
}
}
裝飾器import * as React from 'react'
class Child extends React.Component {
constructor(props) {
super(props)
}
@autoBind
update() {
this.props.onChange('小明名字改了')
}
render() {
return (<div>
{this.props.parentName}
<button onClick={this.update}>更新</button>
</div>)
}
}
裝飾器是es7語法,如果需要使用需要安裝對應的babel:present版本。而typescript則原生支持。
autoBind原理大概就是劫持get方法,get時改變this指向
如何獲得evnt原生事件通過e.nativeEvent獲取原生事件對象
import * as React from 'react'
class Child extends React.Component {
constructor(props) {
super(props)
this.update = this.update.bind(this)
}
update(e) {
console.log(e.nativeEvent)
}
render() {
return (<div>
<button onClick={this.update}>更新</button>
</div>)
}
}
解決冒泡和取消默認事件e.preventDefault() //取消默認行為
e.stopPropagation() //取消冒泡
這個和瀏覽器原生事件處理方案是一致的。問題是我們只可以調合成事件的 e的方法,不可以通過 e.nativeEvent方法做這些操作,原因是上文講過的委託。
ReactDomref特殊的props,ref組件對象的引用,現在官方也不建議直接給ref賦值,需要通過函數來賦值。
ReactDOM.render((
<div>
<Calendar ref={ref => this.c = ref} any-ss="text"/>
</div>
), document.getElementById('root'))
render頂層api,只有在根組件時候才需要使用。第一個參數是Component,第二個參數是dom節點
findDOMNode通過傳入component實例獲取此component根dom節點,在這裡可以去dom節點進行操作了,雖然極其不建議這麼做,但是你確實可以做。
unmountComponentAtNode卸載此組件,並銷毀組件state和事件
接收組件的引用,也就是ref。僅僅是取消掛載,組件還在,如果需要徹底清除的話,需要手動刪掉此dom。
表單onchange配合value與vue框架不同的是,react如果要實現表單元素變化,狀態同步更新,必須要自己去監聽表單事件。
import * as React from 'react'
class Child extends React.Component {
state = {
name: '小明'
}
constructor(props) {
super(props)
this.update = this.update.bind(this)
}
update(e) {
this.setState({
name: e.target.value
})
}
render() {
return (<div>
<input onChange={this.update} value={this.state.name}/>
</div>)
}
}
受控組件和非受控組件受控組件和非受控組件這些都是指的表單組件,當一個表單的值是通過value改變的而不是通過defaultValue是受控組件,否則就是非受控組件。
下面組件中的input就是受控組件
import * as React from 'react'
class Child extends React.Component {
state = {
name: '小明'
}
constructor(props) {
super(props)
this.update = this.update.bind(this)
}
update(e) {
this.setState({
name: e.target.value
})
}
render() {
return (<div>
<input onChange={this.update} value={this.state.name}/>
</div>)
}
}
下面組件中的input是非受控組件
import * as React from 'react'
class Child extends React.Component {
state = {
name: '小明'
}
constructor(props) {
super(props)
this.update = this.update.bind(this)
}
update(e) {
this.setState({
name: e.target.value
})
}
render() {
return (<div>
<input onChange={this.update} defaultValue={this.state.name}/>
</div>)
}
}
組件之間通訊父子之間通訊父子之間通訊又分為父->子,子->父。
因為react單向數據流向的緣故,父->子通信的話直接通過props。父組件數據變動,直接傳遞給子組件。
子->父組件之間就要通過回調函數來通信了,父組件傳遞一個回調函數給子組件,子組件通過調用此函數的方式通知父組件通信。
跨級組件通信react為了實現祖先組件和後輩組件之間的通信問題,引入了contextApi。
class Button extends React.Component {
render() {
return (
<button style={{background: this.context.color}}>
{this.props.children}
</button>
);
}
}
Button.contextTypes = {
color: React.PropTypes.string
};
class Message extends React.Component {
render() {
return (
<div>
{this.props.text} <Button>Delete</Button>
</div>
);
}
}
class MessageList extends React.Component {
getChildContext() {
return {color: "purple"};
}
render() {
const children = this.props.messages.map((message) =>
<Message text={message.text} />
);
return <div>{children}</div>;
}
}
MessageList.childContextTypes = {
color: React.PropTypes.string
};
MessageList中的color會自動更新到兒孫組件裡面去,實現跨級啊通信。如果需要反過來通信,則需要藉助其他工具,比如事件系統(Pub/Sub)。
沒有嵌套關係組件之間通信組件之間通信最主流的兩種方式脫胎於觀察這模式和中介者模式這兩種。
跨級之間通信現在最主流的方式就是觀察這模式的實現Pub/Sub,react社區中的redux也是使用這種方式實現的。
vue2.X版本也去掉了跨組件通信的功能。那如何在2.x中做跨組件通信呢?如果不藉助外力的話,是不是可以使用$parent和$childen的遞歸調用實現全局組件通信呢?比如我想廣播一個事件,我就查找到所有的子組件,挨個觸發$emit(xx),上報一個事件也是同理,只不過需要查找所有的$parent。結合起來就可以實現組件之間的通信,只不過這種查找效率比較低,需要慎用和優化
作者:frontoldman
https://segmentfault.com/a/1190000016281174