react进阶用法完全指南

news2024/11/20 6:27:20

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在线代码

image.png

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和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>
  );
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/81948.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[附源码]Python计算机毕业设计大学生学科竞赛管理系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

【特征选择】时变正弦和 V 形传递函数 (BMPA-TVSinV) 的新型二元海洋捕食者算法附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

CSC7136B

CSC7136B是一款高效率低待机功耗原边反馈小功率 电源 AC/DC 驱动 电路&#xff0c;无 需光耦、TL431 及相关器件。CSC7136B采用开关频率调制和初级电流峰值振幅&#xff08; FM 和 AM &#xff09;多模式工作技术&#xff0c;保证了全负载和线性范围内的较高的转换效率。恒压模…

无法安装人脸检测dlib库的解决方法

1. 引言 dlib 库是一个用来人脸关键点检测的 Python 库&#xff0c;但因为其是 C 编写&#xff08;或需要 C编译&#xff1f;&#xff09;&#xff0c;使得在安装时可能会遇到各种各样问题。笔者在安装时遇到问题后&#xff0c;搜索了一些博客&#xff0c;看到了一些解决方法&…

基于Python+Mysql实现(WinForm)书店销售管理管理子系统【100010028】

书店销售管理管理子系统 一、设 计 总 说 明 现在社会随着计算机技术迅速发展与技术的逐渐成熟&#xff0c;信息技术已经使人们的生活发生深刻的变化。生活中的各种服务系统也使人们在生活中的联系日常销售活动方式发生了很大的变化&#xff0c;让效率较低的手工操作成为过去…

年网络安全观察报告 地域分布

执行摘要 从 1987 年 9 月 14 日&#xff0c;中国向世界发出第一封电子邮件 到如今&#xff0c;中国的互联网发展已过去整整 31 个年头。从消费互联、产业互联到万物互联&#xff0c;互联网正在加速改变我们的交流方式和交易方式&#xff0c;一次次 004.重塑了国家的经济形态和…

Java单例模式的写法及作用介绍

在创建型设计模式中&#xff0c;我们第一个学习的是单例模式&#xff08;Singleton Pattern&#xff09;&#xff0c;这是设计模式中最简单的模式之一。 单例是什么意思呢&#xff1f; 单例就是单实例的意思&#xff0c;即在系统全局&#xff0c;一个类只创建一个对象&#x…

友嘉银行坎坷的云原生之路

随着数字化浪潮的来临&#xff0c;云原生技术越来越火。云原生技术的持续更新&#xff0c;无一不在催促传统行业重塑业务体系以及产业生态的转型升级。说到云原生&#xff0c;目前已被认为是云计算最重要的发展方向&#xff0c;它拥有更优雅的架构、更灵活的调度、更完善的治理…

QT—常用窗口部件

一、QLabel 新建一个QWidget项目&#xff0c;设置基类为QWidget 1.显示文本 调用setText函数编辑要显示的文本&#xff0c;传入一个const QString类型的参数 例如&#xff1a; ui->TxtLabel->setText("Label文本&#xff0c;12345678"); 其中TxtLabel是一…

web大作业 web前端课程设计 web前端课程设计代码 web课程设计 HTML网页制作代码

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

【Android Room 库基础入门】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录一、前言二、Room 主要组件三、Room 基础入门3.1 引入 Room 库到项目3.2 Room 使用示例3.2.1 定义数据实体类3.3.2 定义数据访问对象&#xff08;DAO&#xff09;3.3…

如何成为一名IT咨询顾问?

咨询顾问这个工作&#xff0c;很多人都很羡慕&#xff0c;在别人看来咨询顾问的工作时间自由&#xff0c;可以到处出差和旅游&#xff0c;能够认识企业各种层面的人&#xff0c;见多识广。实际情况真的这么轻松吗&#xff1f;什么样的人适合做咨询顾问&#xff1f;做咨询顾问需…

《计算机网络》2022年期末试卷

《计算机网络》期末试卷2022年A卷

synchronized 关键字

文章目录一、synchronized 的特性互斥可重入二、 synchronized 使用示例三、 java标准库的线程安全类四、 死锁可重入死锁相互争夺锁哲学家就餐问题死锁的四个必要条件一、synchronized 的特性 互斥 synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时…

亚马逊、ebay测评自养号新手应该要怎么做?

如今很多的跨境卖家开始在深入了解测评自养号这块&#xff0c;都想在运营上减低一些成本&#xff0c;多增加一些利润。对于整个测评工作来说&#xff0c;测评技术可谓是非常核心的一个环节&#xff0c;多学习&#xff0c;不要浮躁&#xff0c;这样才能让我们不断提高自己的测评…

Android 3D 魔方游戏的设计与开发

Android 3D 魔方游戏的设计与开发 5.1 Feature 定义 魔方是一个有趣的益智游戏&#xff0c;相信很多人都玩过。本次毕业设计&#xff0c;欲完成的主要的功能如下&#xff1a; &#xff08;1&#xff09; 开始游戏&#xff1a;开始一个新的游戏 &#xff08;2&#xff09; 返…

Java项目:食品检测管理系统(java+SSM+JavaScript+layui+Mysql)

源码获取&#xff1a;俺的博客首页 "资源" 里下载&#xff01; 项目介绍 本项目后台食品检测管理系统&#xff1b; &#xff08;1&#xff09;用户管理&#xff1a;用户登录、验证。 &#xff08;2&#xff09;任务管理&#xff1a;添加任务、检品受理。 &#xff0…

数学基础从高一开始4、集合的基本运算2

数学基础从高一开始3、集合的基本运算2 目录 数学基础从高一开始3、集合的基本运算2 补集 例2&#xff1a; 总结&#xff1a; 补集 这里补集的符号我打不出来&#xff0c;这里就截图给大家看了啊。 下图是补集的语言表达&#xff0c;图形表达以及符号表达方式&#xff1a…

在无需分叉的情况下模拟任何 SIGHASH 标志

我们开发了一种新颖的方法来模拟任何 SIGHASH 标志&#xff0c;只需在智能合约中编写逻辑即可。它不需要更改协议&#xff0c;因此比每次构思新用例时通过分叉添加硬编码标志更实用和灵活。 SIGHASH 标志 SIGHASH 标志决定交易的哪一部分由签名者签名。具体来说&#xff0c;它…

Redis分布式锁那点事

锁超时问题 在redis分布式锁中&#xff0c;如果线程A加锁成功了&#xff0c;但是由于业务功能耗时时间很长&#xff0c;超过了设置的超时时间&#xff0c;这时候redis会自动释放线程A加的锁。通常我们加锁的目的是&#xff1a;为了防止访问临界资源时&#xff0c;出现数据异常…