React基礎(chǔ)入門

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ò)parcelbabel編譯后:

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)行代碼分割。 代碼分割是由諸如 WebpackRollup 和 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 exportReact 組件。

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ò)誤邊界

部分 UIJavaScript 錯(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ò)誤:

  1. 事件處理
  2. 異步代碼
  3. 服務(wù)端渲染
  4. 它自身拋出的錯(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ì)在 componentDidMountcomponentDidUpdate 生命周期鉤子觸發(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)

  1. 不要在 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)丟失。

  1. 務(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 ,你不一定要用名為 renderprop 來(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

HookReact 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,componentDidUpdatecomponentWillUnmount 這三個(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。

參考資料

https://react.html.cn/docs/getting-started.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容