寫在前面
React 的函數組件是 React 組件的另一種定義方式,兩種方式都可以用於定義組件,但是相比於類組件,函數組件要更簡單好用些。
組件名一般要大寫,是為了在組件使用時與一般的 html 標簽區分開
1. 創建方式
函數組件的創建方式就是定義一個函數,這個函數 return React組件。因此,返回一個 React 元素的函數就是組件。在 ES6 語法中,函數有兩種定義方式,普通函數和箭頭函數,因此,函數組件的方式也是兩種。
const Hello = () => {
return <div>Hello Word</div>
}
function Hello(){
return <div>Hello Word</div>
}
2. 外部數據 props
函數接收外部數據的方式就是在函數的參數里拿到,如下:
const Hello = (props) => {
return <div>{props.msg}</div>
}
3. 內部數據 state
函數組件沒有 state,React v16.8.0 推出了 Hooks API,提供了 React.useState
API解決了此問題。
函數組件的 state 數據的設置和獲取是提供了一個接口 React.useState(初始值),用於設置 state 的初始值,對 state 進行初始化。函數里面的參數為初始值,函數返回一個數組,數組的第一個是讀數據方式,第二個是寫數據方式。讀數據變量命名為 xx 時,寫數據方式一般命名為 setXX。
const Welcom = ()=>{
const [n,setN] = React.useState(0);//析構賦值
//等價於
/*
const state = React.useState(0);
const n = state[0];
const setN = state[1];
*/
return (
<div className="wel">
n: {n}
<button onClick={()=>setN(n+1)}>+1</button>
</div>
)
}
或者是先引入 useState,后面就不用再加 React. 了
import React, { useState } from 'react';
const Welcom = ()=>{
const [n,setN] = useState(0);
}
同樣的,setN 也是一個異步函數,不會立馬執行。和 setState 不同的是,setState 是等一會后會改變 state ,而 setN 是永遠不會改變 n,而是產生新的 n。
函數組件的 state 的寫和 class 類組件的寫是完全不同的,函數組件的 setState 不會自動合並之前的值。如下:
const A = ()=>{
const [user,setUser] = React.useState({name: 'jack', age: 18});
const onChangeName = ()=>{
setUser({
//必須加上這一句拷貝之前的數據: ...user,
name: 'lisa' //這樣寫是完全錯誤的,不會自動合並之前的 state
})
}
return (
<div className="App">
<h1>{user.name}</h1>
<h1>{user.age}</h1>
<button onClick={onChangeName}>change name</button>
</div>
)
}
而且,函數組件的 setState 在接受同一個 state 對象時,即使其對象里的屬性變了,但對象地址沒變,是不會更新視圖的。
const A = ()=>{
const [user,setUser] = React.useState({name: 'jack', age: 18});
const onChangeName = ()=>{
user.name = 'lisa',
setUser(user) //錯誤,React 發現接收的對象的地址是同一個,則會認為數據沒有改變,不會更新視圖
}
return (
<div className="App">
<h1>{user.name}</h1>
<h1>{user.age}</h1>
<button onClick={onChangeName}>change name</button>
</div>
)
}
應該產生一個新的 state 對象傳入:
const onChangeName = ()=>{
setUser({
...user,
name: 'lisa'
})
}
函數中的 setState 注意事項:
1. 不會自動合並屬性
2. 地址一定要變
4. 函數組件模擬生命周期
函數組件同樣地沒有生命周期,但是 React Hooks API 提供了 React.useEffect
來解決此問題。
React.useEffect(fn, ??)
第一個參數是在特定時機到的時候執行的回調函數,第二個參數是指明什么時機。
4.1 模擬 componentDidMount 第一次渲染
在第二個參數為 []
時只會在第一次渲染時執行
useEffect(()=>{
console.log("第一次渲染");
},[])
4.2 模擬 componentDidUpdate (不完全模擬)
在第二個參數的數組里加上要監聽變化的數據就行,若不加數組,不傳第二個參數,則會在 state 的任意一個屬性改變時都會觸發該函數回調
useEffect(()=>{
console.log("n 變了");
},[n])
useEffect(()=>{
console.log("任意屬性變了");
})
但是這樣的模擬並不完全等同,因為該函數回調會在一開始在數據由未定義 undefined 到被賦值后會執行該回調函數。而 componentDidUpdate 不會再第一次渲染時執行。
因此可以 自定義 Hook 進行計數,正確模擬 componentDidUpdate 鈎子,自定義 useUpdate.js
如下:
const useUpdate = (fn, dep)=>{
const [count,setCount] = useState(0)
useEffect(()=>{
setCount(x => x + 1);
},[dep])
useEffect(()=>{
if(count > 1){
fn()
}
},[count,fn])
}
4.3 模擬 componentWillUnmount
模擬銷毀前執行時期,是用函數里返回函數的方式。
useEffect(()=>{
console.log("任意屬性變了");
return ()=>{
console.log("該組件要銷毀了")
}
})
4.4 其他組件的模擬
constructor 鈎子的模擬就是在函數組件中,return 語句前的所有語句,這些語句都是在初始化函數組件的數據。
render 鈎子就是函數組件中 return 返回的內容