React调用回调函数,正确设置this指向的三种方法
- 通过bind
this.increment = this.increment.bind(this);
- 通过箭头函数
<button onClick={this.multi}>点我*10</button>
multi = () => {
this.setState({
count: this.state.count * 10
})
}
- 箭头函数包裹
<button onClick={() => {this.muti2()}}>点我*10</button>
绑定事件传递参数
通过箭头函数传递事件参数。
<li onClick={(e) => {this.movie(item,index,e)}}>{item}</li>
条件渲染
- 通过if进行条件判断
const {isLogin} = this.state;
let welcome = null;
if (isLogin) {
welcome = <h2>欢迎回来</h2>
} else {
welcome = <h2>请先登录!</h2>
}
- 使用三目运算符
{isLogin ? <h2>欢迎回来</h2> : <h2>请先登录!</h2> }
- 使用逻辑与
下面这种写法可以省略null。
{isLogin && <h2>你哈欧亚</h2> }
列表渲染
- 使用map高阶函数
{
this.state.movies.map((item,index) => {
return (
<li onClick={(e) => {this.movie(item,index,e)}}> {item} </li>
)
})
}
- 使用filter进行过滤
<ul>
{
this.state.scores.filter(item => {
return item >= 60
})
}
</ul>
- 使用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)
- 安装events库
npm install events
- 创建eventBus对象
const eventBus = new EventEmitter()
- 通过emit发送消息
<button onClick={e => eventBus.emit('sayHello','Hello Home')}>点击向Home组件发送消息</button>
- 通过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>
)
}
}
性能优化
- 函数组件:使用memo
- 类组件:使用pureComponent
使用ref操作DOM
在React的开发模式中,通常情况下不需要直接操作DOM,但是某些特殊情况,确实需要直接对DOM进行操作,此时就需要用到Ref。
注意:下面的几种方法都是在类组件中的
- 字符串形式的ref
<div>
<div ref='titleRef'>Hello,React</div>
<button onClick={e => console.log(this.refs.titleRef.innerHTML = 'Hello Ref')}>点击获取标题的DOM元素</button>
</div>
- 通过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>
);
}
}
- 回调函数形式的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
内联样式
优点:
- 不会有冲突。
- 可以动态获取当前state中的动态。
export default function App() {
const pStyle = {
color: 'pink'
}
return (
<div>
<h2 style={{color: 'red'}}>这是标题</h2>
<p style={pStyle}>这是一段文字</p>
</div>
)
}
缺点:
- 写法上需要使用驼峰标识。
- 某些样式没有提示。
- 大量的样式,代码混乱。
- 某些样式无法编写,例如伪类、伪元素。
组件文件夹下单独引入css
这种方式容易出现样式覆盖的问题。
CSS modules
CSS modules可以有效的解决样式覆盖的问题。
- 在组件文件夹下编写CSS文件,注意后缀是.module.css
- 组件中引入样式
import style from './style.module.css'
- 通过类名的方式使用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。
- 安装
npm install styled-components
- 引入styled-components
import styled from 'styled-components'
- 创建带样式的组件(注意:样式没有加引号)
const Wrapper = styled.h1` color: red;`
- 使用带样式的组件替换原生组件
<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
- 安装库
npm install classnames
- 引入库
import classNames from 'classnames';
- 以对象的形式动态添加className
function App() {
const [isActive] = useState(true);
return (
<div className="App">
<h1 className={classNames({"active": isActive})}>这是APP</h1>
</div>
);
}
如果想要赋值常量,直接传入普通的字符串即可,以逗号分割。
Antd的基本使用
脚手架场景下
- 安装antd
npm install antd
- 引入antd和对应的css样式
import { Button, Space } from 'antd';
import { PoweroffOutlined } from '@ant-design/icons';
import './App.css'
- 根据官网组件的实例代码进行修改。
通过craco对antd主题进行配置
- 安装@craco
npm install @craco
- 自定义主题实际上需要安装下面三个包
"@craco/craco": "^6.4.3",
"babel-plugin-import": "^1.13.5",
"craco-less": "^2.0.0",
"less-loader": "^10.2.0"
- 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的使用和封装
- 安装axios
yarn add axios
- 引入axios
import axios from 'axios'
- 发送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))
- 发送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)
- 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()
}, [])
- 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);
})
- 配置多个请求的共同信息
在引入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'
})
- 创建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))
- 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进行二次封装,主要就是一旦请求不能使用了,只需要修改一个文件即可,同时封装可以减少很多重复代码的编写。
- 创建一个service文件夹
- service文件夹下创建一个request.js
- 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组件存在的问题
-
随着业务的增加,逻辑的复杂,class组件可能会变得越来越复杂,比如componetDidMount中可能包含大量的逻辑代码,这样的class实际上难以拆分,逻辑混在一起,代码的复杂度比较高。
-
class组件中的this指向比较复杂,难以理解。
-
组件复用状态难。例如我们使用Provider、Consumer来共享状态,但是多次使用Consumer时,我们的代码就会存在很多嵌套。
为什么叫做Hook?
Hook直接翻译可能是钩子的意思,意味着这类函数可以帮助我们钩入React的state以及生命周期等特性。
使用Hooks的两个规则
- 只能在函数最外层调用Hook,不要在循环、条件判断、或者子函数中调用。
- 只能在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的核心用法
- 使用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>
)
}
- 函数式组件是不能直接给ref的。
函数组件可以通过React.forwardRef进行包裹来使用ref。
const Test = React.forwardRef((props,ref) => {
return (
<div>
<h1>这是Test组件</h1>
</div>
)
})
- 使用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>
);
}