本文适合零基础的同学、技术栈主 Vue
的同学快速入门 React
,对于有一定的 React
基础同学,相信看完也能有所收获
事实上,css
一直是 React
的痛点,也是被很多开发者吐槽、诟病的一个点。
在这一点上,Vue
做的要好于 React
:
Vue
通过在 .vue
文件中编写 <style><style>
标签来编写自己的样式;scoped
属性来决定编写的样式是全局有效还是局部有效;lang
属性来设置你喜欢的 less、sass
等预处理器;css
;Vue
在 CSS
上虽然不能称之为完美,但是已经足够简洁、自然、方便了,至少统一的样式风格不会出现多个开发人员、多个项目
采用不一样的样式风格。
相比而言,React
官方并没有给出在 React
中统一的样式风格:
css
,到 css modules
,再到 css in js
,有几十种不同的解决方案,上百个不同的库;CSS
方案,但是到目前为止也没有统一的方案;内联样式是官方推荐的一种css样式的写法:
style
接受一个采用小驼峰命名属性的 JavaScript
对象,而不是 CSS
字符串;state
中的状态来设置相关的样式;优点:
state
中的状态缺点
所以官方依然是希望内联合适和普通的 css
来结合编写;
普通的 css
我们通常会编写到一个单独的文件,之后再进行引入。
这样的编写方式和普通的网页开发中编写方式是一致的:
css
都属于全局的 css
,样式之间会相互影响;这种编写方式最大的问题是样式之间会相互层叠掉
css modules
并不是 React
特有的解决方案,而是所有使用了类似于 webpack
配置的环境下都可以使用的。
如果在其他项目中使用它,那么我们需要自己来进行配置,比如配置 webpack.config.js
中的 modules: true
等。
React
的脚手架已经内置了 css modules
的配置:.css/.less/.scss
等样式文件都需要修改成 .module.css/.module.less/.module.scss
等,之后就可以引用并且进行使用了。
css modules
确实解决了局部作用域的问题,也是很多人喜欢在React中使用的一种方案。
但是这种方案也有自己的缺陷:
JavaScript
中是不识别的;className
都必须使用 {style.className}
的形式来编写;如果你觉得上面的缺陷还算OK,那么你在开发中完全可以选择使用css modules来编写,并且也是在React中很受欢迎的一种方式。
官方文档也有提到过 CSS in JS
这种方案, CSS-in-JS
是指一种模式,其中 CSS
由 JavaScript
生成而不是在外部文件中定义,注意此功能并不是 React
的一部分,而是由第三方库提供, React
对样式如何定义并没有明确态度。
而 styled-components
可以说是是社区最流行的一种 CSS-in-JS
库了
styled-components
shnpm i styled-components
styled
的基本使用styled-components
的本质是通过函数的调用,最终创建出一个组件:
class
;styled-components
会给该 class
添加相关的样式;jsimport React, { PureComponent } from "react";
import styled from "styled-components";
const HomeWrapper = styled.div`
color: red;
`;
class App extends PureComponent {
render() {
return (
<HomeWrapper>
<h1>我是标题</h1>
</HomeWrapper>
);
}
}
export default App;
另外,它支持类似于 CSS
预处理器一样的样式嵌套:
&
符号获取当前元素;props可以传递
html<CustomWrapper left="20px">
props
可以被传递给 styled
组件
props
需要通过 ${}
传入一个插值函数,props
会作为该函数的参数;jsconst CustomWrapper = styled.div.attrs({
paddingLeft: props => props.left || '5px'
})`
padding-left: ${props => props.paddingLeft};
`
支持样式的继承
jsconst Parent = styled.button`
padding: 8px 30px;
border-radius: 5px;
`
const Child = styled(Parent)`
background-color: red;
color: #fff;
`
styled 设置主题
jsimport { ThemeProvider } from 'styled-components'
<ThemeProvider theme={{color: "red", fontSize: "30px"}}>
<Home />
<Profile />
</ThemeProvider>
const ProfileWrapper = styled.div`
color: ${props => props.theme.color};
font-size: ${props => props.theme.fontSize}
`
vue中添加class是一件非常简单的事情:
你可以通过传入一个对象:
html<div :class="{active: isAcitive}"></div>
你也可以传入一个数组:
html<div :class="[activeClass, errClass]"></div>
甚至是对象和数组混合使用:
html<div :class="[{active: isActive}, errorClass]"></div>
React在JSX给了我们开发者足够多的灵活性,你可以像编写JavaScript代码一样,通过一些逻辑来决定是否添加某些class:
js<div className={"title " +(isActive ? "active" : "")}> </div>
这个时候我们可以借助于一个第三方的库:classnames
很明显,这是一个用于动态添加classnames的一个库。
JavaScript
开发的应用程序,已经变得越来越复杂了:
JavaScript
需要管理的状态越来越多,越来越复杂;管理不断变化的 state
是非常困难的:
View
页面也有可能会引起状态的变化;state
在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪;React
是在视图层帮助我们解决了 DOM
的渲染过程,但是 State
依然是留给我们自己来管理:
state
,还是组件之间的通信通过 props
进行传递;也包括通过Context
进行数据之间的共享;React
主要负责帮助我们管理视图,state
如何维护最终还是我们自己来决定;UI = rendeer(state)
Redux
就是一个帮助我们管理 State
的容器:Redux
是 JavaScript
的状态容器,提供了可预测的状态管理;
Redux
除了和 React
一起使用之外,它也可以和其他界面库一起来使用(比如Vue
),并且它非常小(包括依赖在内,只有 2kb
)
Redux
的核心理念非常简单。
1. store
比如我们需要一个朋友列表需要管理
products.push
的方式增加了一条数据;products[0].age = 25
修改了一条数据;整个应用程序错综复杂,当出现bug时,很难跟踪到底哪里发生的变化;
jsconst initalState = [
friends: [
{ name: '张三', age: 25 },
{ name: '李四', age: 24 },
]
]
**2. action **
Redux
要求我们通过 action
来更新数据:
action
是一个普通的 JavaScript
对象,用来描述这次更新的 type
和 content
;比如下面就是几个更新friends的action:
jsconst action1 = { type: "ADD_FRIEND", info: {name: 'lucy', age: 20 }}
const action2 = { type: "INC_AGE", index: 0}
const action2 = { type: "CHANGE_NAME", payload: {index:0, newName: 'coder'}}
3. reducer
redux
是如何将state
和action
联系在一起呢?答案就是reducer
reducer
是一个纯函数;reducer
做的事情就是将传入的 state
和 action
结合起来生成一个新的 state
;jsfunction reducer(state = initialState, action) {
switch (action.type) {
case "ADD_FFRIEND":
return { ...state, friends: [...state.friends, action.info] };
case "INC_AGE":
return {
...state,
friend: state.friends.map((item, index) => {
if (index === action.index) {
return { ...item, age: item.age + 1 };
}
}),
};
case "CHANGE_NAME":
return {
...state,
friends: state.friends.map((item, index) => {
if (index === action.index) {
return { ...item, name: action.newName };
}
return item;
}),
};
default:
return state;
}
}
redux-demo
# 执行初始化操作 npm init # 安装 redux npm i redux
src
目录,并创建 index.js
文件package.json
可以执行 index.js
js"scripts": {
"start": "node src/index.js"
}
如果我们将所有的逻辑代码写到一起,那么当redux变得复杂时代码就难以维护。
这里我创建了两个组件:
核心代码主要是两个:
componentDidMoun
t 中定义数据的变化,当数据发生变化时重新设置 counter;store
的 dispatch
来派发对应的 action
;redux
官方提供了 react-redux
的库来帮助我们完成 redux
和 react
的连接,可以直接在项目中使用,并且实现的逻辑会更加的严谨和高效。
jsnpm i react-redux
js// index.js
import { Provider } from 'react-redux'
root.render(
<React.StrictNode>
<Provider store={store}>
<APP />
</Provider>
</React.StrictNode>
)
// Home.js const mapStateToProps = state => ({ counter: state.counter }) const mapDispatchToProps = dispatch => ({ addNumber: function(num) { dispatch(addNumberAction(num)) }, subNumber: function(num) { dispatch(subNumberAction(num)) } }) export default connect(mapStateToProps, mapDispatchToProps)(Home)
事实上,网络请求到的数据也属于我们状态管理的一部分,更好的一种方式应该是将其也交给 redux
来管理
但是在 redux
中如何可以进行异步的操作呢?
这里官网推荐的、包括演示的网络请求的中间件是使用 redux-thunk;
redux-thunk是如何做到让我们可以发送异步的请求呢?
jsnpm i redux-thunk
store
时传入应用了 middleware
的 enhance
函数jsconst enhancer = applyMiddleware(thunkMiddleware)
const store = createStore(reducer,enhancer)
jsconst getHomeMultidataAction = () => {
return dispatch => {
axios.get('xxxxx').then(res => {
const data = res.data.data
dispatch(changeBannersAction(data.banner.list))
})
}
}
Redux Toolkit 是官方推荐的编写 Redux 逻辑的方法。
安装Redux Toolkit:
npm install @reduxjs/toolkit react-redux
Redux Toolkit的核心API主要是如下几个:
目前前端流行的三大框架, 都有自己的路由实现:
React Router在最近两年版本更新的较快,并且在最新的React Router6.x版本中发生了较大的变化。
安装React Router:
npm install react-router-dom
react-router最主要的API是给我们提供的一些组件
BrowserRouter或HashRouter
js<React.StrictMode>
<HashRouter>
<App />
</HashRouter>
</React.StrictMode>
Routes:包裹所有的Route,在其中匹配一个路由。(Router5.x使用的是Switch组件)
Route:Route用于路径的匹配;
js<Routes>
<Route path="/" element={<Home/>}>
<Route path="/about" element={<About/>}>
<Route path="/profile" element={<Profile/>}>
</Routes>
Link和NavLink:
js<div className="header">
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
<Link to="/profile">我的</Link>
</div>
需求:路径选中时,对应的a元素变为红色
这个时候,我们要使用NavLink组件来替代Link组件:
JS<NavLink to="" className={this.getActiveClass}>首页</NavLink>
<NavLink to="about" className={this.getActiveClass}>关于</NavLink>
<NavLink to="profile" className={this.getActiveClass}>我的</NavLink>
jsgetActiveClass({isActive}){
return classNames({ 'link-active': isActive })
}
默认的activeClassName:
Navigate用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中:
我们也可以在匹配到/的时候,直接跳转到/home页面
<Route path="/" element={<Navigate to="/home" />} />
如果用户随意输入一个地址,该地址无法匹配,那么在路由匹配的位置将什么内容都不显示。
很多时候,我们希望在这种情况下,让用户看到一个Not Found的页面。
这个过程非常简单:
<Route path="*" element={<NotFound />} />
<Outlet>
组件用于在父路由元素中作为子路由的占位元素。
在Router6.x版本之后,代码类的API都迁移到了hooks的写法:
如果我们希望进行代码跳转,需要通过useNavigate的Hook获取到navigate对象进行操作;
jsimport { useNavigate } from "react-router-dom";
function MyComponent() {
const navigate = useNavigate();
function handleClick() {
navigate("/path/to/some/page");
}
return (
<button onClick={handleClick}>
Go to page
</button>
);
}
对于类组件,可以使用 withNavigate 高阶组件来包装组件,以便获取 navigate 函数。具体的用法如下:
jsimport { withNavigate } from "react-router-dom";
class MyComponent extends React.Component {
handleClick = () => {
this.props.navigate("/path/to/some/page");
}
render() {
return (
<button onClick={this.handleClick}>
Go to page
</button>
);
}
}
export default withNavigate(MyComponent);
传递参数有二种方式:
动态路由的概念指的是路由中的路径并不会固定:
js<Link to="detail/123">详情:123</Link>
<Link to="user?name=coder">用户信息</Link>
jsconst [searchParams] = useSearchParams()
const query = Object.fromEntries(searchParams)
Hook 是 React 16.8 的新增特性,它可以让我们在不编写class的情况下使用state以及其他的React特性(比如生命周期)。
我们先来思考一下class组件相对于函数式组件有什么优势?比较常见的是下面的优势:
所以,在Hook出现之前,对于上面这些情况我们通常都会编写class组件。
复杂组件变得难以理解:
难以理解的class:
组件复用状态很难:
Hook的出现,可以解决上面提到的这些问题;
简单总结一下hooks:
Hook的使用场景:
在我们继续之前,请记住 Hook 是:
State Hook
的 API
就是 useState
useState
会帮助我们定义一个 state
变量,useState
是一种新方法,它与 class
里面的 this.state
提供的功能完全相同。state
中的变量会被 React
保留。useState
接受唯一一个参数,在第一次组件被调用时使用来作为初始化值。(如果没有传递参数,那么初始化值为undefined)。useState
的返回值是一个数组,我们可以通过数组的解构,来完成赋值会非常方便。我们通过一个计数器案例,来对比一下 class
组件和函数式组件结合 hooks
的对比:
jsimport React, { PureComponent } from "react";
export default class Counter01 extends PureComponent {
constructor(props) {
super(porps);
this.state = {
counter: 0,
};
}
render() {
return (
<div>
<h2>当前计数: {this.state.counter}</h2>
<button onClick={(e) => this.increment()}>+1</button>
<button onClick={(e) => this.decrement()}>-1</button>
</div>
);
}
increment() {
this.setState({ counter: this.state.counter + 1 });
}
decrement() {
this.setState({ counter: this.state.counter - 1 });
}
}
jsimport React, { useState } from "react";
export default function Counter02() {
const [count, setCount] = useState(0);
return (
<div>
<h2>当前计数: {count}</h2>
<button onClick={(e) => setCount(count + 1)}>+1</button>
<button onClick={(e) => setCount(count - 1)}>-1</button>
</div>
);
}
你会发现上面的代码差异非常大
目前我们已经通过hook在函数式组件中定义state,那么类似于生命周期这些呢?
假如我们现在有一个需求:页面的title总是显示counter的数字,分别使用class组件和Hook实现:
js// class 组件
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
incrementCount = () => {
this.setState(prevState => {
return { count: prevState.count + 1 };
});
}
componentDidMount() {
document.title = `Counter: ${this.state.count}`;
}
componentDidUpdate() {
document.title = `Counter: ${this.state.count}`;
}
render() {
return (
<div>
<h1>Counter: {this.state.count}</h1>
<button onClick={this.incrementCount}>Increment</button>
</div>
);
}
}
export default Counter;
js// hook 实现
import React, { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Counter: ${count}`;
});
const incrementCount = () => {
setCount(prevCount => prevCount + 1);
}
return (
<div>
<h1>Counter: {count}</h1>
<button onClick={incrementCount}>Increment</button>
</div>
);
}
export default Counter;
useEffect的解析:
在class组件的编写过程中,某些副作用的代码,我们需要在componentWillUnmount中进行清除:
useEffect传入的回调函数A本身可以有一个返回值,这个返回值是另外一个回调函数B:
jstype EffectCallback = () => (void | (() => void | undefined));
为什么要在 effect 中返回一个函数?
React 何时清除 effect?
使用Hook的其中一个目的就是解决class中生命周期经常将很多的逻辑放在一起的问题:
Hook 允许我们按照代码的用途分离它们, 而不是像生命周期函数那样:
默认情况下,useEffect的回调函数会在每次渲染时都重新执行,但是这会导致两个问题:
但是,如果一个函数我们不希望依赖任何的内容时,也可以传入一个空的数组 []:
在之前的开发中,我们要在组件中使用共享的Context有两种方式:
但是多个Context共享时的方式会存在大量的嵌套:
jsimport React, { useContext } from 'react';
// 创建一个 Context 对象
const ThemeContext = React.createContext('light');
// 在 App 组件中提供一个值为 "dark" 的 Context
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
// 在 Toolbar 组件中使用 Context
function Toolbar() {
const theme = useContext(ThemeContext);
return (
<div>
<button style={{ background: theme }}>按钮</button>
</div>
);
}
注意事项:
很多人看到useReducer的第一反应应该是redux的某个替代品,其实并不是。
useReducer仅仅是useState的一种替代方案:
import React, { useReducer } from 'react'; // 定义一个 reducer 函数来管理 state function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); } } // 定义一个组件,使用 useReducer 管理 state function Counter() { const [state, dispatch] = useReducer(reducer, { count: 0 }); return ( <div> <h1>计数器:{state.count}</h1> <button onClick={() => dispatch({ type: 'increment' })}>+1</button> <button onClick={() => dispatch({ type: 'decrement' })}>-1</button> </div> ); } export default Counter;
数据是不会共享的,它们只是使用了相同的 reducer 的函数而已。
所以,useReducer只是useState的一种替代品,并不能替代Redux。
useCallback 是一个用于缓存函数的 React 钩子函数。它可以优化函数组件的性能,避免因为函数组件的重新渲染导致函数重新定义而产生不必要的计算开销。下面是一个使用 useCallback 的简单示例:
jsimport React, { useState, useCallback } from 'react';
function Counter() {
const [count, setCount] = useState(0);
// 定义一个回调函数,使用 useCallback 缓存
const handleIncrement = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<h1>计数器:{count}</h1>
<button onClick={handleIncrement}>+1</button>
</div>
);
}
export default Counter;
在上面的例子中,我们定义了一个 handleIncrement 回调函数,并使用 useCallback 钩子函数来缓存它。useCallback 接受两个参数,第一个参数是要缓存的函数,第二个参数是一个数组,用来指定缓存函数所依赖的状态变量。
在这个例子中,我们指定了 count 作为依赖项。当 count 发生变化时,useCallback 会返回一个新的缓存函数;当 count 没有变化时,useCallback 返回缓存的旧函数。这样,我们就避免了因为组件的重新渲染导致函数重新定义而产生不必要的计算开销。
最后,在组件中,我们使用缓存的 handleIncrement 函数来处理按钮的点击事件,从而实现了计数器的功能。
useMemo 是一个用于缓存值的 React 钩子函数。它可以优化函数组件的性能,避免因为函数组件的重新渲染导致重复计算的开销。下面是一个使用 useMemo 的简单示例:
jsimport React, { useState, useMemo } from 'react';
function Counter() {
const [count, setCount] = useState(0);
// 定义一个缓存值,使用 useMemo 缓存
const result = useMemo(() => {
let sum = 0;
for (let i = 1; i <= count; i++) {
sum += i;
}
return sum;
}, [count]);
return (
<div>
<h1>计数器:{count}</h1>
<h2>1 到 {count} 的和为:{result}</h2>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
export default Counter;
在上面的例子中,我们定义了一个 result 变量,并使用 useMemo 钩子函数来缓存它。useMemo 接受两个参数,第一个参数是一个函数,用来计算缓存值;第二个参数是一个数组,用来指定缓存值所依赖的状态变量。
在这个例子中,我们指定了 count 作为依赖项。当 count 发生变化时,useMemo 会重新计算缓存值;当 count 没有变化时,useMemo 返回缓存的旧值。这样,我们就避免了因为组件的重新渲染导致重复计算的开销。
最后,在组件中,我们使用缓存的 result 变量来显示计算结果,并通过按钮的点击事件来更新计数器的值,从而实现了计算和的功能。
useRef 是一个用于创建可变引用的 React 钩子函数。它返回一个包含 current 属性的对象,可以用来存储任意可变值,并且不会触发组件的重新渲染。 useRef 可以用来保存 DOM 元素的引用、定时器、以及其他一些需要在组件渲染期间保持稳定的值。下面是一个使用 useRef 的简单示例:
jsimport React, { useState, useRef } from 'react';
function TextInput() {
const [text, setText] = useState('');
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<div>
<input ref={inputRef} value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={handleClick}>聚焦</button>
</div>
);
}
export default TextInput;
在上面的例子中,我们定义了一个 inputRef 引用,并使用 useRef 钩子函数来创建它。在组件中,我们将 input 元素的引用传递给 inputRef,这样就可以在组件中引用 input 元素了。
在 handleClick 函数中,我们使用 inputRef.current.focus() 来聚焦 input 元素。这里,inputRef.current 会返回 input 元素的引用,我们可以通过它来访问 input 元素的属性和方法。
最后,在组件中,我们使用 inputRef 来引用 input 元素,并通过按钮的点击事件来聚焦它。这样,我们就实现了一个可以聚焦输入框的组件。
useImperativeHandle 是一个用于暴露自定义方法到父组件的 React 钩子函数。它可以让子组件向父组件暴露一些 API 方法,以便父组件可以通过子组件的引用来直接调用这些方法。 useImperativeHandle 的第一个参数是一个引用,它可以是任意可变的值,第二个参数是一个返回对象,这个对象包含一些自定义方法。这些自定义方法会被暴露到父组件中,以便父组件可以调用它们。下面是一个使用 useImperativeHandle 的简单示例:
jsimport React, { useRef, useImperativeHandle } from 'react';
function FancyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return (
<div>
<input ref={inputRef} />
</div>
);
}
export default React.forwardRef(FancyInput);
在上面的例子中,我们定义了一个 FancyInput 组件,并使用 useRef 钩子函数来创建 inputRef 引用。在 useImperativeHandle 钩子函数中,我们将 ref 引用传递给它,然后定义了一个 focus 方法,它可以聚焦 input 元素。这样,父组件就可以通过子组件的 ref 引用来调用 focus 方法,从而实现聚焦输入框的功能。
最后,在组件中,我们使用 inputRef 引用来引用 input 元素,并使用 useImperativeHandle 钩子函数来暴露 focus 方法,以便父组件可以通过子组件的 ref 引用来调用它。注意,我们需要使用 React.forwardRef 函数来转发 ref 引用,以便父组件可以访问子组件的 ref 引用。
useLayoutEffect 是 React 中的一个钩子函数,它非常类似于 useEffect,不同的是它会在所有的 DOM 变更之后同步执行,但在浏览器绘制 UI 之前同步执行,这样可以让我们在更新 DOM 之前同步获取 DOM 的尺寸和位置信息,并立即执行其他必要的操作。
和 useEffect 一样,useLayoutEffect 接收两个参数:一个函数和一个依赖项数组,其中第一个参数是需要执行的回调函数,而第二个参数则是一个可选的依赖项数组。当依赖项发生变化时,useLayoutEffect 钩子函数会再次触发执行。下面是一个示例:
jsimport React, { useState, useLayoutEffect, useRef } from 'react';
function App() {
const [width, setWidth] = useState(0);
const boxRef = useRef(null);
useLayoutEffect(() => {
const { current } = boxRef;
if (current) {
setWidth(current.getBoundingClientRect().width);
}
}, []);
return (
<div>
<div ref={boxRef} style={{ width: '100px', height: '100px', background: 'red' }} />
<p>The box width is {width}px.</p>
</div>
);
}
export default App;
在上面的示例中,我们使用了 useLayoutEffect 钩子函数来同步获取一个 div 元素的宽度,并将它存储在 width 状态中。这个 div 元素的引用是通过 useRef 钩子函数创建的,并作为 ref 属性传递给 div 元素。在 useLayoutEffect 回调函数中,我们通过 getBoundingClientRect() 方法获取 div 元素的尺寸信息,并将它的宽度存储在 width 状态中。最后,我们将这个宽度展示在一个 p 元素中。
需要注意的是,由于 useLayoutEffect 会在所有的 DOM 变更之后同步执行,所以在使用它时要非常小心,以免影响应用程序的性能和用户体验。一般来说,应该尽可能地使用 useEffect 钩子函数,只有在确实需要在 DOM 变更之后同步执行操作时,才使用 useLayoutEffect 钩子函数。
自定义 Hook 是 React 中一种重要的代码复用机制。自定义 Hook 允许你在函数组件中复用状态逻辑、副作用逻辑或其他任何组件之间可以共享的逻辑。
一个自定义 Hook 就是一个普通的 JavaScript 函数,但它可以使用其他的 React 钩子函数,如 useState、useEffect、useContext 等。自定义 Hook 的名称以 use 开头,这是 React 的一个约定,它使得我们可以很容易地看出一个 Hook 是不是官方提供的。
下面是一个使用自定义 Hook 的示例:
jsimport React, { useState, useEffect } from 'react';
function useCountdown(initialCount) {
const [count, setCount] = useState(initialCount);
useEffect(() => {
const interval = setInterval(() => {
setCount((prevCount) => prevCount - 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return count;
}
function App() {
const count = useCountdown(10);
return (
<div>
<h1>{count}</h1>
</div>
);
}
export default App;
在上面的示例中,我们定义了一个名为 useCountdown 的自定义 Hook,它使用了 useState 和 useEffect 钩子函数来实现一个倒计时计数器。这个自定义 Hook 接收一个初始计数值,并返回一个当前计数值。然后,我们在组件中调用这个自定义 Hook,得到当前计数值,并将它渲染到页面上。
需要注意的是,自定义 Hook 本身不能包含 JSX 代码,因为它只是一个普通的 JavaScript 函数。但是,我们可以在自定义 Hook 中使用其他的 React 钩子函数来实现组件之间共享的逻辑。
使用自定义 Hook 的好处在于,它可以让我们更好地组织和复用代码,避免了组件之间的代码冗余,提高了代码的可读性和可维护性。
本文作者:叶继伟
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!