写点什么

react 进阶用法完全指南

作者:xiaofeng
  • 2022-10-27
    浙江
  • 本文字数:15055 字

    阅读完需:约 49 分钟

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>        )    }}
复制代码


相关 React 实战视频讲解:进入学习

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 = UserContextfunction 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> ) }}
复制代码


下面是函数式组件的写法


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. 创建一个xxxContextconst 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 和 NavLink

  • 一般路径的跳转使用 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> );}
复制代码


用户头像

xiaofeng

关注

努力写代码中 2022-08-18 加入

努力写代码中

评论

发布
暂无评论
react进阶用法完全指南_React_xiaofeng_InfoQ写作社区