1. 程式人生 > 其它 >react進階用法完全指南

react進階用法完全指南

React呼叫回撥函式,正確設定this指向的三種方法

  1. 通過bind
this.increment = this.increment.bind(this);
  1. 通過箭頭函式
<button onClick={this.multi}>點我*10</button> 

multi = () => {
    this.setState({
        count: this.state.count * 10
    })
}
  1. 箭頭函式包裹
<button onClick={() => {this.muti2()}}>點我*10</button>  

繫結事件傳遞引數

通過箭頭函式傳遞事件引數。

<li onClick={(e) => {this.movie(item,index,e)}}>{item}</li>

條件渲染

  1. 通過if進行條件判斷
const {isLogin} = this.state;
let welcome = null;
if (isLogin) {
    welcome = <h2>歡迎回來</h2>
} else {
    welcome = <h2>請先登入!</h2>
}
  1. 使用三目運算子
{isLogin ? <h2>歡迎回來</h2> : <h2>請先登入!</h2> }
  1. 使用邏輯與

下面這種寫法可以省略null。

{isLogin && <h2>你哈歐亞</h2> }

列表渲染

  1. 使用map高階函式
{
    this.state.movies.map((item,index) => {
        return (
            <li onClick={(e) => {this.movie(item,index,e)}}>                {item}            </li>
        )
    })
}
  1. 使用filter進行過濾
<ul>
    {
        this.state.scores.filter(item => {
            return item >= 60
        })
    }
</ul> 
  1. 使用slice進行擷取

區間是左閉右開。

{
    this.state.scores.slice(0,3).map(item => {
        return <li>{item}</li>
    })
}

腳手架的基本使用

使用腳手架建立專案

  • 專案名稱不能包含大寫字母。
create-react-app demo

元件通訊

1. 父元件向子元件傳遞資料通過props

  • 父元件
export default class App extends Component {
  render() {
    return (
      <div>
          <Child  name ='張三' age="18" />
      </div>
    )
  }
}
  • 子元件
class Child extends Component {
    constructor(props) {
        super()
        this.props = props;
    }
    render() {
        const {name,age} = this.props;
        return (
            <div>子元件獲取到的name是:{name},age是:{age}</div>
        )
    }
}

2. 子元件向父元件傳遞資料通過回撥函式

import React, { Component } from 'react';

class Btn extends Component {
    render() {
        const {increment} = this.props;
        return (
            <button onClick={increment}>+1</button>
        )
    }
}


class App extends Component {
    constructor() {
        super();
        this.state = {
            count: 0
        }
    }
    render() {
        const {count} = this.state;
        return (
            <div>
                <h1>當前求和為:{count}</h1>
                <Btn increment = {e => this.increment()} />            </div>
        );
    }
    increment() {
        console.log(666);
        this.setState({
            count: this.state.count + 1
        })
    }
}

export default App;

3. 跨元件層級通訊(Context)(類元件)

import React, { Component } from 'react'

const UserContext = React.createContext({
    name: '張三',
    age: 20
})

class Sub extends Component {
    render() {
        return (
            <div>
                <h1>name是:{this.context.name }</h1>
                <h1>age是:{this.context.age}</h1>
            </div>
        )
    }
}

Sub.contextType = UserContext
function Profile() {
    return (
        <div>
            <Sub />
            <ul>
                <li>設定1</li>
                <li>設定2</li>
                <li>設定3</li>
                <li>設定4</li>
            </ul>
        </div>
    )
}

export default class App extends Component {
    constructor(){
        super();
        this.state = {
            name: '李四',
            age: 18
        }
    }
    render() {
        return (
            <div>
                <UserContext.Provider value = {this.state}>
                    <Profile />
                </UserContext.Provider>
            </div>
        )
    }
}

參考 React面試題詳細解答

下面是函式式元件的寫法

function Sub(props) {
    return (
        <UserContext.Consumer>
            {                value => {                    return (                        <div>
                            <h1>name是: {value.name}</h1>
                            <h1>age是: {value.age}</h1>
                        </div>
                    )                }            }        </UserContext.Consumer>
    )
}

4. 任意元件通訊(事件匯流排event bus)

  1. 安裝events庫
npm install events
  1. 建立eventBus物件
const eventBus = new EventEmitter()
  1. 通過emit傳送訊息
<button onClick={e => eventBus.emit('sayHello','Hello Home')}>點擊向Home元件傳送訊息</button>
  1. 通過addListener來監聽訊息
eventBus.addListener('sayHello',(args) => {
    this.setState({
        message: args
    })
})
  • 線上CodeSandBox

引數驗證

使用PropTypes進行引數驗證。

import React from 'react'
import PropTypes from 'prop-types'
export default function App() {
    const names = [1,2,3]
    return (
        <div>
            <Cpn name="張三" age={20} names={names} />
        </div>
    )
}

function Cpn(props) {
    const { name, age,names } = props;
    return (
        <div>
            <h1>{name} + {age} + </h1>
            {                names.map(item => item)            }        </div>
    )
}

Cpn.propTypes = {
    names: PropTypes.array,
    age: PropTypes.number.isRequired
}

React實現slot

通過props進行傳遞jsx。

  • 父元件
export default class App extends Component {
  render() {
    return (
      <div>
          <NavBar               leftSlot={<button>111</button>}              centerSlot={<a href="/#">222</a>}              rightSlot={<span>666</span>}          />         </div>
    )
  }
}
  • 子元件
export default class NavBar extends Component {
    render() {
        const {leftSlot,centerSlot,rightSlot} = this.props;
        return (
            <div className='nav-bar'>
                <div className="left">
                    {leftSlot}                </div>
                <div className="center">
                    {centerSlot}                </div>
                <div className="right">
                    {rightSlot}                </div>
            </div>
        )
    }
}

效能優化

  1. 函式元件:使用memo
  2. 類元件:使用pureComponent

使用ref操作DOM

在React的開發模式中,通常情況下不需要直接操作DOM,但是某些特殊情況,確實需要直接對DOM進行操作,此時就需要用到Ref。

注意:下面的幾種方法都是在類元件中的

  1. 字串形式的ref
  <div>
    <div ref='titleRef'>Hello,React</div>
    <button onClick={e => console.log(this.refs.titleRef.innerHTML = 'Hello Ref')}>點選獲取標題的DOM元素</button>
  </div>
  1. 通過createRef
class App extends Component {
  constructor(props) {
    super(props);
    this.titleRef = createRef();
  }
  render() {
    return (
      <div>
        <div ref={this.titleRef}>Hello,React</div>
        <button onClick={e => console.log(this.titleRef.current.innerHTML = '張三')}>點選獲取標題的DOM元素</button>
      </div>
    );
  }
}
  1. 回撥函式形式的Ref
class App extends Component {
  constructor(props) {
    super(props);
    this.titleRef = null;
  }
  render() {
    return (
      <div>
        <div ref={arg => this.titleRef = arg}>Hello,React</div>
        <button onClick={e => console.log(this.titleRef.innerHTML = '張三')}>點選獲取標題的DOM元素</button>
      </div>
    );
  }
}

在函式元件中使用ref,可以通過useRef鉤子函式

function App() {
  const titleRef = useRef();
  return (
    <div>
      <div ref={titleRef}>Hello,React</div>
      <button onClick={e => titleRef.current.innerHTML = '張三'}>點選獲取標題的DOM元素</button>
    </div>
  );
}

受控元件和非受控元件

受控元件

將可變狀態儲存在元件的state屬性中,並且只能通過使用setState來更新,這種元件叫做受控元件。

下面是一個受控元件的例子:

function App() {
  const [msg,setMsg] = useState('');
  useEffect(() => {
   console.log(msg); 
  })
  return (
    <div>
      <form onSubmit={e => handleSubmit(e)}>        <label htmlFor="username">
          使用者:<input                   type="text"                   id="username"                   onChange={e => setMsg(e.target.value)}                   value={msg}                />        </label>
        <input type="submit" value="提交" />
      </form>
    </div>
  );
}

非受控元件

如果要使用非受控元件中的資料,需要使用ref來從DOM節點中獲取表單資料。

高階元件

高階元件是一個接收引數為元件,返回值為新元件的函式。注意:高階元件是一個函式。

下面是一個高階元件的例項:

class App extends PureComponent {
  render() {
    return (
      <div>
        App        {this.props.name}      </div>
    )
  }
}

function enhanceComponent(WrappedComponent) {
  return class newComponent extends PureComponent {
    render() {
      return <WrappedComponent {...this.props} />
    }
  }
}

const EnhanceComponent = enhanceComponent(App)

export default EnhanceComponent

高階元件的應用一:增強props

function Home(props) {
  return (
    <h1>暱稱:{props.nickname}  等級: {props.level} 區域:{props.region}</h1>
  )
}

function enhanceProps(Cpn) {
  return props => {
    return <Cpn {...props} region="中國" />
  }
}

const EnhanceHome = enhanceProps(Home)

class App extends PureComponent {
  render() {
    return (
      <div>
        <EnhanceHome nickname="張三" level="99" />
      </div>
    )
  }
}
export default App

高階元件的其他應用

高階元件還可以用於登入鑑權、生命週期劫持(這裡的生命週期劫持,我們可以理解為計算某個元件的渲染時間)、通過forwardRef高階函式給函式式元件傳遞Ref,具體不再贅述。

portals的使用

portals存在的意義在於,有時候我們想要一個元件獨立於父元件進行渲染,例如這樣的一個場景:父元件的顯示區域比較小,但是我們想要一個元件顯示在螢幕的中間,此時就可以使用portals。

下面這個例子是將Modal元件渲染到螢幕的中間。

function Modal(props) {
  return (
    ReactDOM.createPortal(props.children,document.querySelector('#modal'))
  )
}

function Home(props) {
  return (
    <div>
      <h1>Home</h1>
      <Modal>
        <h2>Title</h2>
      </Modal>
    </div>
  )
}

export default class App extends PureComponent {
  render() {
    return (
      <div>
        <Home />
      </div>
    )
  }
}

fragment

所謂的fragment就是使用空標籤來代替div標籤,防止出現不必要的標籤。

<>
  <h1>當前求和為:{count}</h1>
  <button onClick={e => setCount(count + 1)}>點我+1</button>
</>

下面這種寫法,可以新增屬性,上面的寫法則不行。

<Fragment>
  <h1>當前求和為:{count}</h1>
  <button onClick={e => setCount(count + 1)}>點我+1</button>
</Fragment>

React中的CSS

內聯樣式

優點:

  1. 不會有衝突。
  2. 可以動態獲取當前state中的動態。
export default function App() {
  const pStyle = {
    color: 'pink'
  }
  return (
    <div>
      <h2 style={{color: 'red'}}>這是標題</h2>
      <p style={pStyle}>這是一段文字</p>
    </div>
  )
}

缺點:

  1. 寫法上需要使用駝峰標識。
  2. 某些樣式沒有提示。
  3. 大量的樣式,程式碼混亂。
  4. 某些樣式無法編寫,例如偽類、偽元素。

元件資料夾下單獨引入css

這種方式容易出現樣式覆蓋的問題。

CSS modules

CSS modules可以有效的解決樣式覆蓋的問題。

  1. 在元件資料夾下編寫CSS檔案,注意字尾是.module.css
  2. 元件中引入樣式
import style from './style.module.css'
  1. 通過類名的方式使用css
export default function App() {
  return (
    <div>
      <h1 className={style.title}>這是APP</h1>
    </div>
  )
}

從這種方式我們可以看出明顯要好於單獨寫CSS。但是這種方案也有其缺點,就是引用的類名中不能包含短橫線,這樣無法識別,不方便動態修改某些樣式。

CSS IN JS

CSS-in-JS是一種模式,其中CSS由JS生成而不是在外部檔案中定義,此功能不是React的一部分,而是由第三方庫提供。

目前比較流行的CSS-in-JS庫有:

  • styled-components(使用最多的)
  • emotion
  • glamorous

在使用CSS-in-JS之前,我們需要掌握標籤模板字串的用法,下面是一個經典的例子:

function test(...arg) {
  console.log(arg);  // [['123 is ', ''], '張三']
}

const name = '張三'

test`123 is ${name}`

下面介紹下,如何使用styled-components。

  1. 安裝
npm install styled-components
  1. 引入styled-components
import styled from 'styled-components'
  1. 建立帶樣式的元件(注意:樣式沒有加引號)
const Wrapper = styled.h1`  color: red;`
  1. 使用帶樣式的元件替換原生元件
<Wrapper>這是APP元件</Wrapper>

styled-components也是支援less等寫法的,例如下面的例子:

const Wrapper = styled.div`  color: red;  .banner {    background-color: blue;  }  // 注意:這裡也可以使用props的寫法來獲取動態屬性`

給css-in-js傳遞動態屬性。

import React,{useState} from 'react'
import styled from 'styled-components'

const Wrapper = styled.div.attrs({
  bColor: "red"
})`  background-color: lightblue;  border: 2px solid;  border-color: ${props => props.bColor};  color: ${props => props.color};`

export default function App() {
  const [color] = useState('yellow')
  return (
    <div>
      <Wrapper color={color}>
        這是APP元件        <h2 className="banner">這是H2</h2>
      </Wrapper>
    </div>
  )
}

使用classnames庫給React動態新增className

  1. 安裝庫
npm install classnames
  1. 引入庫
import classNames from 'classnames';
  1. 以物件的形式動態新增className
function App() {
  const [isActive] = useState(true);
  return (
    <div className="App">
      <h1 className={classNames({"active": isActive})}>這是APP</h1>
    </div>
  );
}

如果想要賦值常量,直接傳入普通的字串即可,以逗號分割。

Antd的基本使用

腳手架場景下

  1. 安裝antd
npm install antd
  1. 引入antd和對應的css樣式
import { Button, Space } from 'antd';
import { PoweroffOutlined } from '@ant-design/icons';
import './App.css'
  1. 根據官網元件的例項程式碼進行修改。

通過craco對antd主題進行配置

  1. 安裝@craco
npm install @craco
  1. 自定義主題實際上需要安裝下面三個包
"@craco/craco": "^6.4.3",
"babel-plugin-import": "^1.13.5",
"craco-less": "^2.0.0",
"less-loader": "^10.2.0"
  1. craco.config.js
const CracoLessPlugin = require('craco-less');
module.exports = {
  babel: {
      plugins: [
         [
             "import", 
             {
                 "libraryName": "antd",
                 "libraryDirectory": "es",
                  "style": true //設定為true即是less
              }
          ]
      ]
  },
  plugins: [
      {
          plugin: CracoLessPlugin,
          options: {
              lessLoaderOptions: {
                  lessOptions: {
                      modifyVars: { '@primary-color': '#7d2b21' },
                      javascriptEnabled: true,
                  },
              },
          },
      },
  ],
};

強烈建議使用yarn,不要使用antd。

給資料夾路徑起別名

首先,之所以要給資料夾起別名,就是因為有時候檔案的巢狀層級比較深,不好找到檔案,但是通過給根資料夾起別名則可以很快的找到它們。

在配置檔案中進行如下配置:

const path = require('path')
// 將引數的路徑和當前的路徑進行一個拼接
const resolve = dir => path.resolve(__dirname, dir);
module.exports = {
    webpack: {
        alias: {
            "@": resolve("src"),
            "components": resolve("src/components")
      }
    }
};

引入檔案路徑的時候則可以這樣引入:”

import Test from 'components/Test'

評論元件案例

  • codesandbox線上程式碼

axios的使用和封裝

  1. 安裝axios
yarn add axios
  1. 引入axios
import axios from 'axios'
  1. 傳送get請求
axios({
  url: "https://httpbin.org/get",
  params: {
    name: '張三',
    age: 20
  }
}).then(res => {
  console.log(res);
}).catch(err => {
  console.log(err);
})

還可以通過下面的方式:

axios.get("https://httpbin.org/get", {
  params: {
    name: '張三',
    age: 20
  }
}
).then(res => console.log(res))
  1. 傳送post請求
axios({
  url: "https://httpbin.org/post",
  data: {
    name: 'edge',
    age: 0
  },
  method: "post"
}).then(res => {
  console.log(res);
}).catch(err => {
  console.error(err)
})

也可以通過下面這種方式:

axios.post("https://httpbin.org/post", {
  data: {
    name: 'edge',
    age: 0
  }
}).then(console.log)
  1. axios結合async和await
  useEffect(() => {
    async function fetchData() {
      const result = await axios.post("https://httpbin.org/post", {
        data: {
          name: 'edge',
          age: 0
        }
      })
      console.log('111',result);
    }
    fetchData()
  }, [])
  1. axios.all的使用
const request1 = axios({
  url: "https://httpbin.org/get",
  params: {name: "test",age: 20}
})

const request2 = axios({
  url: "https://httpbin.org/post",
  data: {name: 'kobe',age: 66},
  method: 'post'
})

axios.all([request1,request2]).then(([res1,res2]) => {
  console.log('axios.all:',res1,res2);
}).catch(err => {
  console.log(err);
})
  1. 配置多個請求的共同資訊

在引入axios的地方進行如下配置:

axios.defaults.baseURL = "https://httpbin.org";
axios.defaults.timeout = 5000;
axios.defaults.headers.common["token"] = "dfasdfkajndsfkjndsf";
axios.defaults.headers.post["Content-type"] = "application/text"

配置後的請求則可以這樣寫:

const request1 = axios({
  url: "/get",
  params: {name: "test",age: 20}
})

const request2 = axios({
  url: "/post",
  data: {name: 'kobe',age: 66},
  method: 'post'
})
  1. 建立axios例項來實現個性化請求不同的伺服器

上面我們提到了建立公共請求的配置資訊,但是有時候我們想要請求的URL可能是不同的地址,此時就需要個性化的配置了。

const instance2 = axios.create({
  baseURL: "http://baidu.xyz",
  timeout: 1000
})
instance2.get('/get',{
  params: {data: "test"}
}).then(res => console.log(res)).catch(err => console.log(err))
  1. axios攔截器
axios.interceptors.request.use(config => {
  // 1. 可以在這個位置設定顯示loading元件
  // 2. 給請求新增token
  // 3. 對params進行序列化的操作
  console.log('攔截成功');
  config.headers.token = JSON.stringify({ name: 'ty' });
  return config
}, err => {

})

// // 響應攔截器
axios.interceptors.response.use(res => {
  // res.data = 666;
  console.log('響應攔截器攔截成功');
  return res
}, err => {

})

axios.get('https://httpbin.org/get', {
  params: { name: 'justin' }
}).then(console.log).catch(console.log)

axios.post('https://httpbin.org/post', {
  data: { name: 'justin6366666666' }
}).then(res => console.log('響應:',res)).catch(console.log)

二次封裝axios

之所以要對axios進行二次封裝,主要就是一旦請求不能使用了,只需要修改一個檔案即可,同時封裝可以減少很多重複程式碼的編寫。

  1. 建立一個service資料夾
  2. service資料夾下建立一個request.js
  3. service資料夾下建立一個config.js(用於書寫axios的公共配置資訊)

config.js中可以寫下面的配置資訊:

const devBaseURL = "https://httpbin.org";
const proBaseURL = "https://production.org";

export const BASE_URL = process.env.NODE_ENV === 'development' ? devBaseURL : proBaseURL;

export const TIMEOUT = 5000;

request.js中可以寫下面的請求方法:

import axios from "axios";

import {BASE_URL,TIMEOUT} from './config'

const instance = axios.create({
  baseURL: BASE_URL,
  timeout: TIMEOUT
})

export default instance

React Hooks

為什麼需要Hooks?

Hook是React16.8中新增的特性,它可以讓我們在不編寫class的情況下使用state以及其他的React特性。

在Hook出現之前,函式式元件相對於class元件有如下劣勢:

  • class元件可以定義自己的狀態,函式式元件不可以。
  • class元件有自己的生命週期,函式式元件則會每次重新渲染都重新發送一次網路請求。
  • 函式式元件在重新渲染時整個函式都會被執行。

class元件存在的問題

  1. 隨著業務的增加,邏輯的複雜,class元件可能會變得越來越複雜,比如componetDidMount中可能包含大量的邏輯程式碼,這樣的class實際上難以拆分,邏輯混在一起,程式碼的複雜度比較高。

  2. class元件中的this指向比較複雜,難以理解。

  3. 元件複用狀態難。例如我們使用Provider、Consumer來共享狀態,但是多次使用Consumer時,我們的程式碼就會存在很多巢狀。

為什麼叫做Hook?

Hook直接翻譯可能是鉤子的意思,意味著這類函式可以幫助我們鉤入React的state以及生命週期等特性。

使用Hooks的兩個規則

  1. 只能在函式最外層呼叫Hook,不要在迴圈、條件判斷、或者子函式中呼叫。
  2. 只能在React的函式式元件中呼叫Hook,不能在JS函式中呼叫。

useState的核心用法

useState可以接收一個函式,也可以接收一個值,如果是函式,其可以拿到前一個狀態,但是返回的要是最新的狀態,如果是值的話,就應該是返回的最新狀態。

<button onClick={e => setCount(precount => precount + 1)}>點選+1</button>
<button onClick={e => setFriends([...friends,'匿名'])}>點選新增朋友</button>

useEffect的核心用法

useEffect主要是用來模擬生命週期。

  • useEffect在一個函式元件中可以定義多個,並按照順序執行。
  useEffect(() => {
    console.log('修改DOM');
  })

  useEffect(() => {
    console.log('訂閱事件');
  },[])
  • 檢測某個狀態發生變化的時候才執行回撥函式。
  useEffect(() => {
    console.log('訂閱事件');
  },[count])

useContext的核心用法

// 1. 建立一個xxxContext
const countContext = createContext();

// 2. 通過xxxContext.Provider 包裹傳遞value給目標元件
function App() {
    return (
        <countContext.Provider value={666}>
            <Foo />
        </countContext.Provider>
    )
}
// 3. 目標元件通過useContext(xxxContext)獲取value傳遞的值
function Foo() {
    const count = useContext(countContext)
    return (
        <div>
            {count}        </div>
    )
}

useReducer的核心用法

useReducer是useState的一種替代方案。

const reducer = (state,action) => {
  switch (action.type) {
    case "increment":
      return {...state,count: state.count + 1}
    default:
      return state;
  }
}

export default function Home() {
  const [count,dispatch] = useReducer(reducer,{count: 0});
  return (
    <div>
        <h1>當前求和為:{count.count}</h1>
        <button onClick={e => dispatch({type: 'increment'})}>點我+1</button>
    </div>
  )
}

useCallback的核心用法

useCallback會返回一個函式的memorized值,在依賴不變的情況下,多次定義的時候,返回的值是相同的。

useCallback想要解決的問題是這樣的,假如一個函式元件中有一個函式,只要狀態發生改變,這個函式都會被重新定義,十分浪費效能,並且可能帶來不好的影響。

useCallback結合memo可以進行效能優化,確保傳入的是相同的函式例項。useCallback如果依賴項是一個空陣列,則只會執行一次,返回的都是相同的函式例項,如果有依賴項的話,則是依賴項發生變化才返回新的例項。

常見的使用場景是:將一個函式傳遞給元件進行回撥時,可以進行效能優化。

export default function CallbackDemo() {
  console.log('CallbackDemo被重新渲染');
  const [count, setCount] = useState(0);
  const [show,setShow] = useState(true);
  const increment1 = useCallback(() => {
    console.log('increment1函式執行了~');
    setCount(count + 1);
  })

  const increment2 = useCallback(() => {
    console.log('increment2函式執行了~');
    setCount(count + 1);
  }, [count])
  return (
    <div>
      <h1>當前求和為:{count}</h1>
      <MyButton title="btn1" increment={increment1} />
      <MyButton title="btn2" increment={increment2} />
      <button onClick={e => setShow(!show)}>點選切換show</button>
    </div>
  )
}

useMemo的核心用法

useMemo的核心也是為了效能優化。

  • useMemo返回的也是一個快取的值。
  • 依賴不變的情況下,多次定義的時候,返回的值是相同的。

下面的這個例子可以很好的說明useMemo的核心用法,可以有效的避免calc函式不必要的重新計算。

const calc = (num) => {
  console.log('重新計算');
  let temp = 0;
  for (let i = 1; i <= num; i++) {
    temp += i;
  }
  return temp;
}

export default function MemoHookDemo() {
  const [count, setCount] = useState(10);
  const [show,setShow] = useState(true);
  // 避免calc函數出現不必要的重新計算
  const total = useMemo(() => {
    return calc(count);
  },[count])
  return (
    <div>
      <h1>當前求和為:{total}</h1>
      <button onClick={e => setCount(count + 1)}>點選+1</button>
      <button onClick={e => setShow(!show)}>點選切換</button>
    </div>
  )
}

useMemo還可以避免子元件不必要的重新渲染。(結合了memo)

const MyButton = memo(props => {
  console.log('子元件重新渲染');
  return (
    <h2>子元件收到的props:{props.info.name}</h2>
  )
})

export default function MemoHookDemo02() {
  console.log('父元件重新渲染');
  const [show,setShow] = useState(false);
  const info = useMemo(() => {
    return {name: "漫威"}
  },[])
  return (
    <div>
      <MyButton info={info} />
      <button onClick={e => setShow(!show)}>點選切換</button>
    </div>
  )
}

useRef的核心用法

  1. 使用ref引用DOM。
export default function RefHook() {
  const titleRef = useRef();

  const changeDOM = () => {
    titleRef.current.innerHTML = "引用DOM"
  }
  return (
    <div>
      <h2 ref={titleRef}>這是useRef的核心用法</h2>
      <button onClick={e => changeDOM()}>點選切換</button>
    </div>
  )
}
  1. 函式式元件是不能直接給ref的。

函式元件可以通過React.forwardRef進行包裹來使用ref。

const Test = React.forwardRef((props,ref) => {
  return (
    <div>
      <h1>這是Test元件</h1>
    </div>
  )
})
  1. 使用useRef跨足劍週期儲存資料
export default function RefHook() {
  const [count,setCount] = useState(0);
  const countRef = useRef(count);

  return (
    <div>
      <h2>useRef中儲存的值:{countRef.current}</h2>
      <h2>這是count的值:{count}</h2>
      <button onClick={e => setCount(count + 1)}>點選+1</button>
    </div>
  )
}

useImperativeHandle的核心用法

之所以要有useImperativeHandle這個鉤子函式,是為了防止父元件通過ref獲取到子元件的所有許可權,通過useImperativeHandle可以讓子元件指定對外暴露的功能。

const Son = forwardRef((props,ref) => {
  const inputRef = useRef();

  useImperativeHandle(ref,() => ({
    focus: () => {
      inputRef.current.focus();
    }
  }))

  return (
    <input type="text" ref={inputRef} />
  )
})

export default function RefHook() {
  const sonRef = useRef()
  return (
    <div>
      <Son ref={sonRef} />
      <button onClick={e => sonRef.current.focus()}>點選聚焦</button>
    </div>
  )
}

useLayoutEffect的核心用法

useLayoutEffect和useEffect的區別主要有以下兩點:

  • useEffect是在DOM更新完成之後執行,不會阻塞DOM的更新。
  • useLayoutEffect會在更新DOM之前執行,會阻塞DOM的更新。

如果希望在某些操作發生之後再去更新DOM,那麼這個操作應該放在useLayoutEffect中執行。主要是解決閃爍問題。

export default function LayoutDemo() {
  const [count,setCount] = useState(10);

  // useEffect會在渲染之後執行
  // useEffect(() => {
  //   if (count === 0) {
  //     setCount(Math.random());
  //   }
  // },[count])

  // useLayoutEffect會在渲染之前執行
  useLayoutEffect(() => {
    if (count === 0) {
      setCount(Math.random());
    }
  },[count])

  return (
    <div>
      <h1>當前隨機數為:{count}</h1>
      <button onClick={e => setCount(0)}>點選設定為隨機數</button>
    </div>
  )
}

自定義Hook的核心用法(主要還是用於邏輯複用)

自定義Hook的本質是一種函式程式碼邏輯的抽取。自定義元件必須以use開頭,否則會報錯。

下面的這個自定義Hook就是對元件的掛載和解除安裝中重複的邏輯進行復用。

export default function CustomHook() {
  useInfo('CustomHook');
  return (
    <div>
      <h1>這是測試自定義Hook</h1>
    </div>
  )
}

function useInfo(name) {
  useEffect(() => {
    console.log(`${name}元件被掛載了~`);
    return () => {
      console.log(`${name}元件被解除安裝了~`);
    };
  }, []);
}

自定義Hook和普通的函式封裝的區別在於,自定義Hook可以使用預設的Hooks,類似於useState等,但是普通的函式不能使用,這也就是為什麼自定義Hook在命名時需要以use開頭。

react-router的核心用法

安裝react-router-dom

yarn add react-router-dom

react-router中最核心的API

BrowserRouter和HashRouter

  • Router中包含了對路徑改變的監聽,並且會將相應的路徑傳遞給子元件。
  • BrowserRouter使用History模式。
  • HashRouter使用Hash模式。
  • 一般路徑的跳轉使用Link元件,其最終會被渲染成a元素。
  • NavLink是在Link基礎上增加一些樣式屬性。
  • to屬性,指定跳轉到的路徑。

Route

  • Route用於路徑的匹配
  • path屬性:用於設定匹配到的路徑。
  • component屬性:設定匹配到的路徑後,渲染的元件。
  • exact:精準匹配,只有精準匹配到完全一致的路徑,才會渲染對應的元件。

基本使用

下面使用的是一些新特性:

export default function RouteTest() {
  return (
    <div>
      <BrowserRouter>
        <Link to="/" >首頁</Link>
        <Link to="/about" >關於</Link>

        <Routes>
          {/* <Route path="/" component={Home} />
          <Route path="/about" component={About} /> */}          {/* 下面是React18的新語法 */}          <Route path="/" element={<Home />} />          <Route path="/about" element={<About />} />        </Routes>

      </BrowserRouter>
    </div>
  )
}

注意:Link都會顯示成a標籤,但是並不是所有的Route都會顯示,Route所在的區域就是命中路由的元件要顯示的區域。我們可以把Route理解為佔位符。

react-router的不同版本的特點都是不一樣的,因此,有些特定功能的用法一定要根據版本去官網查用法,例如下面的這個給選中的link改變顏色,就是通過這個版本對應的官網查到的。

  • react-router-V6
export default function RouteTest() {
  let activeStyle = {
    color: "red",
  };
  return (
    <div>
      <BrowserRouter>
        <Routes>
          {/* <Route path="/" component={Home} />
          <Route path="/about" component={About} /> */}          {/* 下面是React18的新語法 */}          <Route exact path="/" element={<Home />} />          <Route exact path="/about" element={<About />} />        </Routes>


        <NavLink to="/"           style={({ isActive }) =>
          isActive ? activeStyle : undefined        }>          首頁        </NavLink>
        <NavLink to="/about"
          style={({ isActive }) =>
            isActive ? activeStyle : undefined          }>          關於        </NavLink>
      </BrowserRouter>
    </div>
  )
}

需要注意的是在react-router(V6)版本中Switch已經被Routes取代了。

路由重定向

重定向和Link的區別在於,Link是需要使用者點選的,重定向可以是JS執行的。

在V6版本的react-router-dom中重定向Redirect已經被Navicat這個API取代了、

import {Navigate} from 'react-router-dom'

const User = () => {
  const [isLogin] = useState(false);
  return isLogin ? (
    <div>
      <h1>這是User元件</h1>
    </div>
  ) : <Navigate to="/login"/>;
}

動態路由

需要注意的是,設定動態路由的時候最好在某個路徑下使用,而不是直接就是一個動態路由,那樣容易出現攔截到意外路由的情況。

<Route  path="/user/:id" element={<User />} />
  • 使用useParams獲取動態路由的值。
import {Navigate,useParams} from 'react-router-dom'

const User = () => {
  const [isLogin] = useState(true);
  const params = useParams();
  console.log(params);
  return isLogin ? (
    <div>
      <h1>這是User元件</h1>
    </div>
  ) : <Navigate to="/login"/>;
}
  • 使用useSearchParams獲取查詢字串(通過原型物件上的get方法來獲取值)
import {Navigate,useSearchParams} from 'react-router-dom'

const User = () => {
  const [isLogin] = useState(true);
  const [searchParams,setSearchParams] = useSearchParams();
  console.log(searchParams);
  console.log(searchParams.get('name'));
  return isLogin ? (
    <div>
      <h1>這是User元件</h1>
    </div>
  ) : <Navigate to="/login"/>;
}
  • 使用遍歷的方式獲取到所有的查詢字串
import {Navigate,useSearchParams} from 'react-router-dom'

const User = () => {
  const [isLogin] = useState(true);
  const [searchParams,setSearchParams] = useSearchParams();
  searchParams.forEach((item,key) => {
    console.log(item,key);
  })
  console.log(searchParams.get('name'));
  return isLogin ? (
    <div>
      <h1>這是User元件</h1>
    </div>
  ) : <Navigate to="/login"/>;
}

使用react-router-config簡化路由的編寫

具體可以通過檢視官網使用。

  • react-router-config

巢狀路由

巢狀路由我們可以理解為路由中的路由。(需要使用Outlet進行佔位,具體看下面的連結中的文章。)

  <BrowserRouter>
    <Routes>
      <Route exact path="/about" element={<About />}>        <Route path="culture" element={<AboutCulture />} />        <Route path="contact" element={<AboutContact />} />      </Route>
  </BrowserRouter>
  • react-router v6 使用(這篇文章講的特別好)

手動路由跳轉

在react-router-dom 6版本中history這個API被useNavigate取代了。

const About = () => {
  const navigate = useNavigate()
  return (
    <div>
      <NavLink to="/about/culture">企業文化</NavLink>
      <NavLink to="/about/contact">聯絡我們</NavLink>
      <button onClick={e => navigate('/about/join')}>點選加入我們吧~</button>
      <Outlet />

    </div>
  );
}