JSX
JSX 是在JavaScript 語(yǔ)法上的拓展,允許 HTML 代碼和 JS 一起寫。
///單行代碼
const heading = <h1>Mozilla Developer Network</h1>;
///多行代碼
const header = (
<header>
<h1>Mozilla Developer Network</h1>
</header>
);
///heading/header 常量稱為 JSX 表達(dá)式
///React 可以使header在我們的應(yīng)用程序中進(jìn)行渲染
JSX瀏覽器無(wú)法直接讀取并解析,JSX表達(dá)式,經(jīng)過(guò)parcel或babel編譯后:
const header = React.createElement("header", null,
React.createElement("h1", null, "Mozilla Developer Network")
);
實(shí)際開發(fā)中也可以跳過(guò)編譯步驟,直接使用React.createElement()構(gòu)建UI。
創(chuàng)建React應(yīng)用
npx包運(yùn)行器;npm包管理器
創(chuàng)建react應(yīng)用并啟動(dòng)
# 創(chuàng)建react應(yīng)用模板
npx create-react-app react-demo
# 如果既有yarn又有npm,在創(chuàng)建時(shí)可以指定使用哪個(gè)創(chuàng)建
npx create-react-app react-demo --use-npm
#啟動(dòng)react應(yīng)用
cd react-demo
#運(yùn)行應(yīng)用在http://localhost:3000
npm start
在React中,組件是組成應(yīng)用程序的可重復(fù)利用的模塊。
組件 & Props
函數(shù)組件與class組件
// 函數(shù)組件
function TestComponent(props) {
return <h1> Hi , {props.value} </h1>
}
///ES6 class組件
class TestComponent extends React.Component {
render(){
return <h1> Hi , {this.props.value} </h1>
}
}
注意: 組件名稱必須以大寫字母開頭。小寫字母開頭的組件視為原生 DOM 標(biāo)簽
Props 的只讀性
///純函數(shù),a、b的值不會(huì)被修改
function sum(a, b) {
return a + b;
}
所有React 組件都必須像純函數(shù)一樣保護(hù)它們的 props 不被更改。
State&生命周期
///計(jì)時(shí)器組件
class Clock extends React.Component {
///構(gòu)造函數(shù)
constructor(props) {
super(props)
///初始時(shí)間狀態(tài) & 計(jì)數(shù)器狀態(tài)
this.state = {
date: new Date(),
counter: 1
}
}
///返回組件的JSX
render(){
return (
<div>
<h1> 當(dāng)前時(shí)間:{this.state.date.toLocaleTimeString()} </h1>
<h2> 時(shí)間更新次數(shù):{this.state.counter}</h2>
</div>
)
}
///生命周期函數(shù)
///組件已經(jīng)被渲染到DOM中時(shí)調(diào)用
componentDidMount() {
const timerHandler = ()=>{
///錯(cuò)誤示范
// this.setState({
// counter: this.state.counter + this.props.increment,
// });
//聯(lián)合更新,使用箭頭函數(shù),方式如下:
this.setState((state,props)=>({
date: new Date(),
counter: state.counter += parseInt(props.increment)
}))
// 聯(lián)合更新,使用普通函數(shù),方式如下:
this.setState(function(state,props){
return {
date: new Date(),
counter: state.counter += parseInt(props.increment)
}
})
///簡(jiǎn)單更新
this.setState({
date: new Date()
})
}
///啟動(dòng)定時(shí)器
this.timerId = setInterval(timerHandler,2000)
console.log("componentDidMount")
}
///組件更新時(shí)調(diào)用
componentDidUpdate(){
console.log("componentDidUpdate")
}
///組件被銷毀之前調(diào)用
componentWillUnmount(){
clearInterval(this.timerId)
console.log("componentWillUnmount")
}
}
State 的更新可能是異步的,因此不能使用在setState中使用this.state。
組件可以選擇把它的 state 作為 props向下傳遞到它的子組件中:
<FormattedDate date={this.state.date} />
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
事件處理
- React 事件的命名采用小駝峰式(camelCase),而不是純小寫
- 使用 JSX 語(yǔ)法時(shí)你需要傳入一個(gè)函數(shù)作為事件處理函數(shù),而不是一個(gè)字符串
<--- 傳統(tǒng)html --->
<button onclick="activateLasers()">
Activate Lasers
</button>
<---React寫法--->
<button onClick={activateLasers}>
Activate Lasers
</button>
事件添加
///普通函數(shù)
class LoggingButton extends React.Component {
constructor(){
super()
///傳建一個(gè)和原函數(shù)體相同的函數(shù),這個(gè)函數(shù)和this進(jìn)行綁定
this.handleClick = this.handleClick.bind(this)
}
// 若不綁定嚴(yán)格模式下,未bind,則this指向undifined
handleClick(){
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
///或者直接綁定
<button onClick={handleClick.bind(this)}>
Click me
</button>
);
}
}
///箭頭函數(shù)
class LoggingButton extends React.Component {
// 此語(yǔ)法確保 `handleClick` 內(nèi)的 `this` 已被綁定。
// 注意: 這是 *實(shí)驗(yàn)性* 語(yǔ)法。
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
向事件處理函數(shù)傳參
///普通函數(shù)
handleClick(id){
console.log('this is:', this);
console.log(id)
}
<button onClick={this.handleClick.bind(this,2)}>
Click me
</button>
///箭頭函數(shù)
<button onClick={()=>{this.handleClick(2)}}>
Click me
</button>
條件渲染
元素變量
//聲明一個(gè)變量并使用 if 語(yǔ)句進(jìn)行條件渲染
render() {
const isLoggedIn = this.state.isLoggedIn;
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
&&運(yùn)算符
JavaScript 中,true && expression 總是會(huì)返回 expression, 而 false && expression 總是會(huì)返回 false。如果條件是 true,&& 右側(cè)的元素就會(huì)被渲染,如果是 false,React 會(huì)忽略并跳過(guò)它。
render() {
const count = 0;
return (
<div>
{count && <h1>Messages: {count}</h1>}
</div>
);
}
三目運(yùn)算符
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn
? <LogoutButton onClick={this.handleLogoutClick} />
: <LoginButton onClick={this.handleLoginClick} />
}
</div>
);
}
阻止組件渲染
在極少數(shù)情況下,你可能希望能隱藏組件,即使它已經(jīng)被其他組件渲染。若要完成此操作,你可以讓 render 方法直接返回 null,而不進(jìn)行任何渲染。
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return (
<div className="warning">
Warning!
</div>
);
}
///其他組件引用`WarningBanner`,當(dāng)warn為空,該組件不被渲染
render() {
return (
<div>
<WarningBanner warn={this.state.showWarning} />
<button onClick={this.handleToggleClick}>
{this.state.showWarning ? 'Hide' : 'Show'}
</button>
</div>
);
}
列表&Key
const root = ReactDOM.createRoot(document.getElementById('root'));
const numbers = [1, 2, 3, 4, 5];
root.render(
<React.StrictMode>
<List values={numbers} />
</React.StrictMode>
);
function List(param) {
///集合的`Map`轉(zhuǎn)換
///key 幫助 React 識(shí)別哪些元素改變了
const listItem = param.values.map((num,index)=><li key={index} >{num}</li> )
return <ul>{listItem}</ul>
///或者
return <ul>{param.values.map((num,index)=><li key={index} >{num}</li> )}</ul>
}
用Key提取組件
元素的 key只有放在就近的數(shù)組上下文中才有意義。
比方說(shuō),如果你提取出一個(gè) ListItem 組件,你應(yīng)該把 key 保留在數(shù)組中的這個(gè) <ListItem /> 元素上,而不是放在 ListItem 組件中的 <li> 元素上。
錯(cuò)誤示例:
function ListItem(props) {
const value = props.value;
return (
// 錯(cuò)誤!你不需要在這里指定 key:
<li key={value.toString()}>
{value}
</li>
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 錯(cuò)誤!元素的 key 應(yīng)該在這里指定:
<ListItem value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
正確示例:
///正確示例
function ListItem(props) {
// 正確!這里不需要指定 key:
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 正確!key 應(yīng)該在數(shù)組的上下文中被指定
<ListItem key={number.toString()} value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
組合&繼承
組件使用一個(gè)特殊的 children 屬性, 來(lái)將他們的子組件傳遞到組件中。
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
function WelcomeDialog() {
return (
<FancyBorder color="blue">
///children
<h1 className="Dialog-title">
Welcome
</h1>
</FancyBorder>
);
}
也可以自定義屬性
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.content}
</div>
);
}
function WelcomeDialog() {
const content = <h1 className="Dialog-title"> Welcome </h1>
return (
<FancyBorder color="blue" content={content} />
);
}
代碼分割
為了避免搞出大體積的代碼包,需要進(jìn)行代碼分割。 代碼分割是由諸如 Webpack,Rollup 和 Browserify(factor-bundle)這類打包器支持的一項(xiàng)技術(shù),能夠創(chuàng)建多個(gè)包并在運(yùn)行時(shí)動(dòng)態(tài)加載。
import()
///使用之前
import { add } from './math';
console.log(add(16, 26));
///使用之后
import("./math").then(math => {
console.log(math.add(16, 26));
});
React.lazy
React.lazy 函數(shù)能讓我們像渲染常規(guī)組件一樣處理動(dòng)態(tài)引入的組件。
///before
import OtherComponent from './OtherComponent';
///after
const OtherComponent = React.lazy(() => import('./OtherComponent'));
此代碼將會(huì)在組件首次渲染時(shí),自動(dòng)導(dǎo)入包含 OtherComponent 組件的包。React.lazy 接受一個(gè)函數(shù),這個(gè)函數(shù)需要?jiǎng)討B(tài)調(diào)用 import()。它必須返回一個(gè) Promise,該 Promise 需要 resolve 一個(gè) default export 的 React 組件。
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() {
return (
<div>
//fallback 屬性接受任何在組件加載過(guò)程中你想展示的 React 元素
<Suspense fallback={<div>正在加載...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</div>
);
}
Context
Context 提供了一個(gè)無(wú)需為每層組件手動(dòng)添加 props,就能在組件樹間進(jìn)行數(shù)據(jù)傳遞的方法。實(shí)現(xiàn)了一個(gè)組件樹“全局”數(shù)據(jù)的共享。
通過(guò)掛載在class 上的 contextType 屬性可以賦值為由 React.createContext() 創(chuàng)建的 Context 對(duì)象。此屬性可以讓你使用 this.context 來(lái)獲取最近 Context 上的值。
///創(chuàng)建context并設(shè)置初始值
const ThemeContext = React.createContext('light')
export default class App extends React.Component {
render() {
return (
///ThemeContext.Provider 指定value修改初始值
<ThemeContext.Provider value = 'dark'>
<TabBar />
</ThemeContext.Provider>
)
}
}
// 中間的組件不必指明往下傳遞 props
export class TabBar extends React.Component {
render() {
return <Navigation />
}
}
/// 葉子節(jié)點(diǎn)直接獲取
export class Navigation extends React.Component {
///必須以`contextType`進(jìn)行指定,使得`this.context`能夠獲取
static contextType = ThemeContext;
render() {
console.log(Navigation.contextType)
return (
<p> Naigation 的主題 : <span> {this.context} </span></p>
)
}
}
消耗多個(gè)context
// Theme context,默認(rèn)的 theme 是 “l(fā)ight” 值
const ThemeContext = React.createContext('light');
// 用戶登錄 context
const UserContext = React.createContext({
name: 'Guest',
});
class App extends React.Component {
render() {
const {signedInUser, theme} = this.props;
// 提供初始 context 值的 App 組件
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}
function Layout() {
return (
<div>
<Sidebar />
<Content />
</div>
);
}
// 一個(gè)組件可能會(huì)消費(fèi)多個(gè) context
function Content() {
return (
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
錯(cuò)誤邊界
部分 UI 的 JavaScript 錯(cuò)誤不應(yīng)該導(dǎo)致整個(gè)應(yīng)用崩潰,為了解決這個(gè)問(wèn)題,React 16 引入了一個(gè)新的概念 —— 錯(cuò)誤邊界。
錯(cuò)誤邊界是一種 React 組件,這種組件可以捕獲發(fā)生在其子組件樹任何位置的 JavaScript 錯(cuò)誤,并打印這些錯(cuò)誤,同時(shí)展示降級(jí) UI,而并不會(huì)渲染那些發(fā)生崩潰的子組件樹。錯(cuò)誤邊界可以捕獲發(fā)生在整個(gè)子組件樹的渲染期間、生命周期方法以及構(gòu)造函數(shù)中的錯(cuò)誤。
注意:錯(cuò)誤邊界無(wú)法捕獲以下場(chǎng)景中產(chǎn)生的錯(cuò)誤:
- 事件處理
- 異步代碼
- 服務(wù)端渲染
- 它自身拋出的錯(cuò)誤
示例代碼1:
//定義的錯(cuò)誤邊界組件
export default class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = {
errorInfo: null,
error: null
}
}
componentDidCatch(err, errInfo) {
// Catch errors in any components below and re-render with error message
this.setState({
error: err,
errorInfo: errInfo,
})
// You can also log error messages to an error reporting service here
}
render() {
if (this.state.errorInfo) {
return (
<div>
<h2> 出錯(cuò)了 </h2>
<p> {this.state.error && this.state.error.toString()} </p>
<p> {this.state.errorInfo && this.state.errorInfo.toString()} </p>
</div>
)
}
return this.props.children
}
}
///使用函數(shù)組件
function Counter(params) {
///"React.useState" cannot be called in a class component,
/// must be called in a React function component or a custom React Hook function
const [count, setCount] = React.useState(0)
function click1() {
let x = count + 1
setCount(x)
}
if (count === 2) {///模仿錯(cuò)誤
throw new Error("Crashed")
}else{
return <button onClick={click1}> {count} </button>
}
}
示例代碼2
///定義邊界組件
export default class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = {
hasError : false
}
}
static getDerivedStateFromError(error) {
return { hasError: true }
}
render() {
if (this.state.hasError) {
return <h2> 出錯(cuò)了 </h2>
}
return this.props.children
}
}
///使用類組件
export default class Counter extends React.Component{
constructor(props) {
super(props);
this.state = { counter:0 }
}
handleClick = ()=>{
///***********注意括號(hào)******
this.setState(({counter}) => ({
counter: counter + 1
}));
}
render() {
return <button onClick={this.handleClick}> {this.state.counter} </button>
}
}
最終使用
///代碼分割,懶加載
const Error = React.lazy(() => import('./ErrorBoundary.jsx'))
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<p> text1 </p>
///與懶加載配合
<Suspense fallback={<div>加載中...</div>} >
<Error>
<Counter />
</Error>
</Suspense>
<p> text2 </p>
</React.StrictMode>
);
注意:自 React 16 起,任何未被錯(cuò)誤邊界捕獲的錯(cuò)誤將會(huì)導(dǎo)致整個(gè) React 組件樹被卸載。
Refs
Refs 提供了一種方式,允許我們?cè)L問(wèn)DOM節(jié)點(diǎn)或在 render 方法中創(chuàng)建的 React元素。
Refs 是使用 React.createRef() 創(chuàng)建的,并通過(guò) ref 屬性附加到 React 元素。在構(gòu)造組件時(shí),通常將 Refs分配給實(shí)例屬性,以便可以在整個(gè)組件中引用它們。
///為DOM元素添加`Ref`
export default class RefComponent extends Component {
constructor(props) {
super(props);
///創(chuàng)建`Ref`
this.pRef = React.createRef()
}
onClick = ()=>{
const pNode = this.pRef.current ///獲取pNode
pNode.textContent = '通過(guò)Refs操作添加的文本' ///操作元素
}
render() {
return (
<div>
<p ref={this.pRef}></p>
<button onClick={
this.onClick
} > 按鈕 </button>
</div>)
}
}
React 會(huì)在組件掛載時(shí)給 current 屬性傳入 DOM元素,并在組件卸載時(shí)傳入 null 值。ref 會(huì)在 componentDidMount或 componentDidUpdate 生命周期鉤子觸發(fā)前更新。
// 為`React`的class組件`RefComponent` 添加`ref`。
// 在組件加載后,通過(guò)該組件的`ref`,執(zhí)行其內(nèi)部的點(diǎn)擊事件,觸發(fā)組件內(nèi)部<P>的更新
export class AutoRefComponet extends Component {
constructor(props) {
super(props);
this.comRef = React.createRef()
}
componentDidMount(){
this.comRef.current.onClick()
}
render() {
return <RefComponent ref={this.comRef}/>;
}
}
AutoRefComponet通過(guò)Ref操作RefComponent組件,實(shí)現(xiàn)其內(nèi)部P標(biāo)簽的更新,僅當(dāng)RefComponent組件為Class組件時(shí)有效。
默認(rèn)情況下,不能在函數(shù)組件上使用 ref 屬性,但是可通過(guò)Refs轉(zhuǎn)發(fā)或?qū)⒑瘮?shù)組件轉(zhuǎn)換為Class組件來(lái)使用Ref,前提條件這個(gè)ref只能指向一個(gè)DOM 元素或 class 組件。
///舉例:函數(shù)組件內(nèi)部使用`ref`,非屬性
export default function RefComponent(){
///創(chuàng)建
const pRef = useRef(null)
function onClick(){
const pNode = pRef.current ///獲取pNode
pNode.textContent = '通過(guò)Refs操作添加的文本' ///操作元素
}
return (<div>
<p ref={pRef}></p>
<button onClick={onClick} > 按鈕 </button>
</div>
)
}
Refs回調(diào)
另一種設(shè)置ref的方式,不同于傳遞 createRef() 創(chuàng)建的 ref 屬性,需要傳遞一個(gè)函數(shù)。這個(gè)函數(shù)中接受 React組件實(shí)例或 HTML DOM 元素作為參數(shù),以使它們能在其他地方被存儲(chǔ)和訪問(wèn)。
export default class RefComponent extends Component {
constructor(props) {
super(props);
///存儲(chǔ)pnode
this.pNode = null
///創(chuàng)建ref 回調(diào)
this.pRef = (element) => {
///返回元素 dom or react 實(shí)例
this.pNode = element
}
}
onClick = ()=>{
this.pNode.textContent = '通過(guò)Refs操作添加的文本' ///操作元素
}
render() {
return (
<div>
<p ref={this.pRef}></p>
<button onClick={this.onClick} > 按鈕 </button>
</div>)
}
}
React 將在組件掛載時(shí),會(huì)調(diào)用 ref 回調(diào)函數(shù)并傳入 DOM 元素,當(dāng)卸載時(shí)調(diào)用它并傳入 null。
可以在組件間傳遞回調(diào)形式的 refs,就像你可以傳遞通過(guò) React.createRef() 創(chuàng)建的對(duì)象 refs 一樣。
export default class RefComponent extends Component {
render() {
return (
<div>
<p ref={this.props.pref}></p>
<button onClick={this.onClick} > 按鈕 </button>
</div>)
}
}
// 為`React`的class組件,添加`ref`回調(diào)
export class AutoRefComponet extends Component {
componentDidMount(){
this.pNode.textContent = '通過(guò)Refs操作添加的文本'
}
render() {
/// 注意:不能是ref,換個(gè)名
return <RefComponent pref={(element) => {
this.pNode = element
}}/>;
}
}
Refs轉(zhuǎn)發(fā)
修改一個(gè)子組件,需要使用新的props 來(lái)重新渲染它,但是某些情況下需要強(qiáng)制修改,被修改的子組件可能是React組件或DOM元素。
比如上述的AutoRefComponet組件通過(guò)ref直接引用RefComponet組件,從而操作子組件, 16.3 或更高版本的 React,推薦使用Ref轉(zhuǎn)發(fā)。 建議盡量使用狀態(tài)提升來(lái)處理。
Ref 轉(zhuǎn)發(fā)是一個(gè)可選特性,其允許某些組件接收 ref,并將其向下傳遞(換句話說(shuō),“轉(zhuǎn)發(fā)”它)給子組件。
///普通的函數(shù)組件另一種定義形式
const RefComponent = (props) => (
<div>
<p> {props.value} </p>
</div>
)
///ref轉(zhuǎn)發(fā)示例
///子組件
const RefComponent = React.forwardRef((props, ref) => (
<div>
<p ref={ref}></p>
<p> {props.value} </p>
</div>)
)
// 父組件
export class AutoRefComponet extends Component {
constructor(props) {
super(props)
this.getPref = React.createRef()
}
componentDidMount() {
this.getPref.current.textContent = '通過(guò)Refs轉(zhuǎn)發(fā)操作添加的文本'
}
render() {
return <RefComponent ref = {this.getPref} value='匪夷所思的用法' />;
}
}
Fragment
///橫向列表顯示 hello world
<table>
<tr>
<td>Hello</td>
<td>World</td>
</tr>
</table>
///上述方式比較復(fù)雜,React提供了簡(jiǎn)單的方式
class Columns extends React.Component {
render() {
return (
<React.Fragment>
<td>Hello</td>
<td>World</td>
</React.Fragment>
);
}
}
///簡(jiǎn)寫
class Columns extends React.Component {
render() {
return (
<>
<td>Hello</td>
<td>World</td>
</>
);
}
}
高階組件
高階組件(HOC)是 React 中用于復(fù)用組件邏輯的一種高級(jí)技巧。HOC 自身不是React API 的一部分,它是一種基于 React 的組合特性而形成的設(shè)計(jì)模式。
高階組件是參數(shù)為組件,返回值為新組件的函數(shù)。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
高階組件
高階組件(HOC)是 React 中用于復(fù)用組件邏輯的一種高級(jí)技巧。HOC 自身不是React API 的一部分,它是一種基于 React 的組合特性而形成的設(shè)計(jì)模式。
高階組件是參數(shù)為組件,返回值為新組件的函數(shù)。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
注意事項(xiàng)
- 不要在 render 方法中使用 HOC
如果從 render 返回的組件與前一個(gè)渲染中的組件相同(===),則 React 通過(guò)將子樹與新子樹進(jìn)行區(qū)分來(lái)遞歸更新子樹。 如果它們不相等,則完全卸載前一個(gè)子樹。
render() {
// 每次調(diào)用 render 函數(shù)都會(huì)創(chuàng)建一個(gè)新的 EnhancedComponent
// EnhancedComponent1 !== EnhancedComponent2
const EnhancedComponent = enhance(MyComponent);
// 這將導(dǎo)致子樹每次渲染都會(huì)進(jìn)行卸載,和重新掛載的操作!
return <EnhancedComponent />;
}
不僅僅出現(xiàn)性能問(wèn)題 ,重新掛載組件會(huì)導(dǎo)致該組件及其所有子組件的狀態(tài)丟失。
- 務(wù)必復(fù)制靜態(tài)方法
React組件上定義靜態(tài)方法很有用,但將 HOC 應(yīng)用于組件時(shí),原始組件將使用容器組件進(jìn)行包裝,新組件會(huì)沒有原始組件的任何靜態(tài)方法。
// 定義靜態(tài)函數(shù)
WrappedComponent.staticMethod = function() {/*...*/}
// 現(xiàn)在使用 HOC
const EnhancedComponent = enhance(WrappedComponent);
// 增強(qiáng)組件沒有 staticMethod
typeof EnhancedComponent.staticMethod === 'undefined' // true
解決辦法
function enhance(WrappedComponent) {
class Enhance extends React.Component {/*...*/}
// 必須準(zhǔn)確知道應(yīng)該拷貝哪些方法 :(
Enhance.staticMethod = WrappedComponent.staticMethod;
return Enhance;
}
render props
render prop 是一個(gè)用于告知組件需要渲染什么內(nèi)容的函數(shù) prop
///圖片組件,跟著鼠標(biāo)移動(dòng)
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
///鼠標(biāo)移動(dòng)的組件
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
{/*
使用 `render`prop 動(dòng)態(tài)決定要渲染的內(nèi)容,
而不是給出一個(gè) <Mouse> 渲染結(jié)果的靜態(tài)表示
*/}
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>移動(dòng)鼠標(biāo)!</h1>
///告訴mouse組件需要渲染cat組件,也可以指定其他的組件
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}
重要的是要記住,render prop是因?yàn)槟J讲疟环Q為 render prop ,你不一定要用名為 render 的 prop 來(lái)使用這種模式。事實(shí)上任何被用于告知組件需要渲染什么內(nèi)容的函數(shù) prop 在技術(shù)上都可以被稱為 render prop
類型檢查
const root = ReactDOM.createRoot(document.getElementById('root'));
const tmp = [12,33,2]
root.render(
<React.StrictMode>
<MyComponent name={tmp} />
</React.StrictMode>
);
///定義
import PropTypes from 'prop-types'
export default class MyComponent extends Component {
///設(shè)置默認(rèn)值
static defaultProps = {
name : "我的組件"
}
///類型校驗(yàn)
static propTypes = {
name : PropTypes.string
}
constructor(props) {
super(props);
}
render() {
return (
<div>{this.props.name}</div>
);
}
}
///函數(shù)組件使用
import PropTypes from 'prop-types'
function HelloWorldComponent({ name }) {
return (
<div>Hello, {name}</div>
)
}
///類型校驗(yàn)
HelloWorldComponent.propTypes = {
name: PropTypes.string
}
///設(shè)置默認(rèn)值
HelloWorldComponent.defaultProps = {
name: "我的組件"
}
export default HelloWorldComponent
非受控組件
在 React 中,<input type="file" /> 始終是一個(gè)非受控組件,因?yàn)樗闹抵荒苡捎脩粼O(shè)置,而不能通過(guò)代碼控制。
///通過(guò)DOM節(jié)點(diǎn),讀取數(shù)據(jù)
class FileInput extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.fileInput = React.createRef();
}
handleSubmit(event) {
event.preventDefault();
alert(
`Selected file - ${this.fileInput.current.files[0].name}`
);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Upload file:
<input type="file" ref={this.fileInput} />
</label>
<br />
<button type="submit">Submit</button>
</form>
);
}
}
ReactDOM.render(
<FileInput />,
document.getElementById('root')
);
HOOK
Hook 是React 16.8 的新增特性。它可以讓你在不編寫class的情況下使用state 以及其他的 React特性
即,可以讓你在函數(shù)組件里“鉤入” React state 及生命周期等特性的函數(shù)。
useState
關(guān)于函數(shù)組件
const Example = (props) => {
return <div />;
}
///or
function Example(props) {
return <div />;
}
函數(shù)組件中使用useState 會(huì)返回一對(duì)值:當(dāng)前狀態(tài)和更新它的函數(shù),我們可以在事件處理函數(shù)中或其他一些地方調(diào)用這個(gè)函數(shù),可重設(shè)狀態(tài)。
///函數(shù)組件使用useState實(shí)現(xiàn)狀態(tài)管理
///"React.useState" cannot be called in a class component,
/// must be called in a React function component or a custom React Hook function
function Counter(params) {
/// 聲明一個(gè)叫 “count” 的 state 變量
const [count, setCount] = React.useState(0)
function click1() {
let x = count + 1
///狀態(tài)更新
setCount(x)
}
///狀態(tài)讀取
return <button onClick={click1}> {count} </button>
}
///等價(jià)的class組件
///使用類組件
export default class Counter extends React.Component{
constructor(props) {
super(props);
this.state = { counter:0 }
}
handleClick = ()=>{
///***********注意括號(hào)******
this.setState(({counter}) => ({
counter: counter + 1
}));
//***或者可以這樣***
this.setState((state) => ({
counter: state.counter + 1
}));
}
render() {
return <button onClick={this.handleClick}> {this.state.counter} </button>
}
}
惰性初始State
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
JavaScript解構(gòu)賦值語(yǔ)法
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30, 40, 50]
({ a, b } = { a: 10, b: 20 });
console.log(a); // 10
console.log(b); // 20
({a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40});
console.log(a); // 10
console.log(b); // 20
console.log(rest); // {c: 30, d: 40}
///useState的語(yǔ)法 like this
function f() {
return [1, 2];
}
let a, b;
[a, b] = f();
console.log(a); // 1
console.log(b); // 2
useEffect
useEffect 可看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 這三個(gè)函數(shù)的組合。具體定義:
type EffectCallback = () => (void | Destructor);
/*
@param effect 接收一個(gè)函數(shù),該函數(shù)返回值為void,或返回一個(gè) cleanup 函數(shù)
@param deps 可選參數(shù),若有,effect函數(shù)僅在該列表中的值發(fā)生變化時(shí),才會(huì)被執(zhí)行
*/
function useEffect(effect: EffectCallback, deps?: DependencyList): void;
useEffect在第一次渲染之后和每次更新之后都會(huì)執(zhí)行。React 保證了每次運(yùn)行effect 的同時(shí),DOM 都已經(jīng)更新完畢。
function Counter(props) {
const [count, setCount] = React.useState(0)
function handleClick() {
setCount(count + 1)
}
///每次渲染都會(huì)執(zhí)行effect, 每次effect都會(huì)清除上次的effect
React.useEffect(()=>{
document.title = `組件掛載|更新后,計(jì)數(shù)為:${count}`
const cleanUp = ()=>{
console.log(`組件卸載,計(jì)數(shù)為:${count}`);
}
return cleanUp
},[count])
return <button onClick={handleClick}> {count} </button>
}
///等價(jià)的class組件
export class Counter extends React.Component{
constructor(props) {
super(props);
this.state = { count:0 }
}
componentDidMount(){
document.title = `組件掛載后,計(jì)數(shù)為:${this.state.count}`
}
componentDidUpdate(prevProps,prevState) { ///點(diǎn)擊觸發(fā)
if (prevState.count !== this.state.count) {
document.title = `組件更新時(shí),計(jì)數(shù)為${this.state.count}`
}
}
componentWillUnmount(){
document.title = `組件卸載時(shí),計(jì)數(shù)為${this.state.count}`
}
handleClick = ()=>{
this.setState((state) => ({
count: state.count + 1
}));
}
render() {
return <button onClick={this.handleClick}> {this.state.count} </button>
}
}
多Effect
多個(gè) Effect 實(shí)現(xiàn)關(guān)注點(diǎn)分離
function Form() {
// 1. Use the name state variable
const [name, setName] = useState('Mary');
// 2. Use an effect for persisting the form
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
// 3. Use the surname state variable
const [surname, setSurname] = useState('Poppins');
// 4. Use an effect for updating the title
useEffect(function updateTitle() {
document.title = name + ' ' + surname;
});
}
自定義HOOK
通過(guò)自定義Hook,可以將組件邏輯提取到可重用的函數(shù)中。
在 React 中有兩種流行的方式來(lái)共享組件之間的狀態(tài)邏輯: render props 和高階組件
自定義 Hook 是一個(gè)函數(shù),其名稱以 “use” 開頭,函數(shù)內(nèi)部可以調(diào)用其他的 Hook
import { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
//通過(guò)friendID,查詢是否在線
});
return isOnline;
}
///使用自定義的HOOK
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
更多HOOK API。