組件是 React 里非常重要的組成部分,其分為函數組件和 Class 組件。本文就簡單說明這兩種組件定義方式的由來。
例子
讓我們先從一個簡單的需求開始。定義一個加減器,就是用來做簡單的加減法。使用 JSX 語法我們可以寫成這樣:
let number = 0
let add = () => {
number += 1
render()
}
let minus = () => {
number -= 1
render()
}
let render = () => {
ReactDOM.render(
<div className="parent"> // Adder1
<span className="red">{number}</span>
<button onClick={add}>+</button>
<button onClick={minus}>-</button>
</div>,
document.querySelector('#root'))
}
render()
函數組件
現在我們想要更多的加減器,那么可能會在 render()
函數里寫很多個 <div className="parent">...</div>
,這樣明顯不好。
還記得 JSX 語法里的并不是真正的 HTML,而是虛擬 DOM,即 JS 代碼,不信可看轉譯后的結果:
既然是 JS 代碼,那么我們就可以用 JS 的方法來將其分塊了。我們定義多個函數,來返回虛擬 DOM 不就可以完成分塊了么?所以定義兩個函數:
function App() {
return (
<div>
<Adder1/> // React.createElement(Adder1)
<Adder2/> // React.createElement(Adder2)
</div>
)
}
function Adder1() {
return (
<div className="parent">
<span className="red">{number}</span>
<button onClick={add}>+</button>
<button onClick={minus}>-</button>
</div>
)
}
...
雖然這里有個問題,變量 number
被 Adder1 和 Adder2 共享了。但是 React 的一個簡單組件就誕生了,其本質就是一個函數。
props
React 的開發者很聰明,即然這個組件返回的是虛擬 DOM,那么正常的 DOM 應該要有屬性才行,而函數的參數好像和這屬性有著某種相關性。所以函數組件的一個特性被開發出來了:函數組件傳入的參數(對象) 代表了該虛擬 DOM 的屬性。例子:
function Adder (props) {
return (
<div className="parent">
<p>My name is {props.name}, age: {props.age}</p>
</div>
)
}
function App() {
return (
<div>
<Adder name="Adder 1" age="12"/>
</div>
)
}
state
那么自身的屬性呢?很簡單,在函數里面定義就好了:
function Adder1(props) {
let name = 'hello'
return (
<div className="parent">
<p>My name is {name}, age: {props.age}</p>
</div>
)
}
Class 組件
好了,現在說說怎么去解決共享 number
的問題。像上面說的用自身屬性試試:
function Adder1(obj) {
let number = 0
function add() {...}
function minus() {...}
return (
<div className="parent">
<span className="red">{number}</span>
<button onClick={add}>+</button>
<button onClick={minus}>-</button>
</div>
)
}
...
雖然好像看起來沒問題,但是每次修改值后我們都要重新 render 一下組件的,render 的時候需要執行 Adder1
里面的代碼,number
被重置了。
React 的 Class 組件就了為了這樣的問題而誕生的:
class Adder1 extends React.Component {
constructor(props) {
super(props)
this.state = {
number: 0
}
}
add() {
this.setState({
number: this.state.number + 1
})
}
minus() {
this.setState({
number: this.state.number - 1
})
}
render() {
return (
<div className="red">
<span>{this.state.number}</span>
<button onClick={this.add.bind(this)}>+1</button>
<button onClick={this.minus.bind(this)}>-1</button>
{this.props.name}
</div>
)
}
}
注意
- 必須要繼承
React.Component
-
constructor
里要傳入props
,并調用super(props)
,因為這是 ES6 語法的規定 -
this.state
用來存放自身屬性,但是修改的時候要用this.setState(newState)
,而不能直接this.state.number += 1
- 綁定方法的時候要綁定
this
,因為 React 會這樣調用this.add.call(undefined, ...)
setState
為了對更新進行優化,如果多次修改 this.state
會將大批量更新合并成一次更新。其實 this.state
的更新方法屬于異步更新,只有全部改變完了再去更新。
像下面的代碼只會更新一次:
this.setState({
number: this.state.number - 1
})
this.setState({
number: this.state.number - 1
}) // 只是一次 - 1,因為每二次的時候 this.state.number 還可能是 0
當然可以用回調的形式進行多次更新:
this.setState((state) => {
return { number: state.number - 1 }
})
this.setState((state) => {
return { number: state.number - 1 }
})