1.React介绍
React由Meta公司开发,是一个用于构建Web和原生交互界面的库
1.1 React优势
相较于传统基于DOM开发的优势
1.组件化的开发方式
2.不错的性能
相较于其他前端框架的优势
1.丰富的生态
2.跨平台支持
1.2React的时长情况
全球最流行,大厂必备
2.开发环境创建
create-react-app是一个快速创建React开发环境的工具,底层由Webpack构件,封装了配置细节,开箱即用执行命令:
npx create-react-app react-basic
cd react-basic
npm start
创建项目更多方式:
https://zh-hans.react.dev/learn/start-a-new-react-project
3.JSX基础
3.1什么是JSX
JSX是JavaScript和XML(HTML)的缩写,表示在JS代码中编写HTML模板结构,是React构建UI的方式
const message = 'this is message'
function App() {
return (
<div className="App">
<h1>My First React App</h1>
{message}
</div>
);
}
export default App;
3.2 JSX的本质
JSX并不是标准的JS语法,它是 JS的语法扩展,浏览器本身不能识别,需要通过解析工具做解析之后才能在浏览器中使用
3.3JSX高频场景-JS表达式
在JSX中可以通过
<font style="color:rgb(119, 119, 119);background-color:rgb(248, 248, 248);">大括号语法{}</font>
识别JavaScript中的表达式,比如常见的变量、函数调用、方法调用等等
使用引号传递字符串、JS变量、函数调用和方法调用、对象
注意:不能使用条件语句
const message = 'this is message'
function getAge(){
return 18
}
function App() {
return (
<div className="App">
<h1>My First React App</h1>
{message}
{/*函数调用*/}
{getAge()}
</div>
);
}
export default App;
3.4JS高频场景-列表场景
const list = [
{id:1001, name:'Vue'},
{id:1002, name: 'React'},
{id:1003, name: 'Angular'}
]
function App(){
return (
<ul>
{list.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
export default App;
3.5JS高频场景-条件渲染
在React中,可以通过逻辑与运算&&、三元表达式(?:)实现基础的条件渲染
const flag = true;
const loading = true;
function App() {
return (
<>
{flag && <span>this is span</span>}
{loading ? <span>loading...</span>:<span>this is span</span>}
</>
)
}
export default App;
3.6JSX高频场景-复杂条件渲染
需求:列表中需要根据文章的状态适配
const type = 0; // 0 1 3
function getArticleJSX() {
if (type === 0) {
return <div>无图模式</div>
} else if (type === 1) {
return <div>单图模式</div>
} else if(type === 3)
{
return <div>多图模式</div>
}
}
function App() {
return (
<>
{getArticleJSX()}
</>
)
}
export default App;
4.React的事件绑定
4.1基础实现
React中的事件绑定,通过语法on + 事件名称 = {事件处理程序}。整体上遵循驼峰命名
function App() {
const clickHandle = () =>{
console.log('button按钮点击了');
}
return (
<button onClick={clickHandle}>click me</button>
)
}
export default App;
4.2使用事件参数
在事件回调函数中设置形参e即可
function App() {
const clickHandle = (e) =>{
console.log('button按钮点击了', e);
}
return (
<button onClick={clickHandle}>click me</button>
)
}
export default App;
4.3传递自定义参数
语法:事件绑定的位置改造成箭头函数的写法,再执行clickHandler实际处理业务函数的时候传递实参
function App() {
const clickHandle = (name) =>{
console.log('button按钮点击了', name);
}
return (
<button onClick={()=>clickHandle('jack')}>click me</button>
)
}
export default App;
注意:不能直接写函数调用,这里事件绑定需要一个函数引用
4.4同时传递事件对象的自定义参数
语法:在事件绑定位置传递事件实参e的自定义参数,clickHandle中声明形参,注意顺序对应
function App() {
const clickHandle = (name, e) =>{
console.log('button按钮点击了', name, e);
}
return (
<button onClick={(e)=>clickHandle('jack', e)}>click me</button>
)
}
export default App;
5.React组件基础使用
5.1 组件是什么
概念:一个组件就是一个用户界面的一部分,它可以有自己的逻辑和外观,组件之间可以互相嵌套,也可以服用多次
5.2组件基础使用
在React中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图UI,渲染增加只需要把组件当作标签书写即可
function Button() {
return <button>click me</button>
}
function App() {
return (
<div>
<Button/>
<Button></Button>
</div>
)
}
export default App;
6.组件状态管理-useStatue
6.1基础使用
useState是一个React Hook(函数),它允许我们向组件添加一个状态变量,从而控制影响组件的渲染结果和普通的JS变量不同的是,状态变量一旦发生变化组件的视图UI也会跟着发生变化(数据驱动试图)
import React from "react";
function App() {
const [count, setCount] = React.useState(0)
return (
<div>
<button onClick={()=>setCount(count + 1)}>{count}</button>
</div>
)
}
export default App;
6.2状态的修改规则
在React中状态被认为是只读的,我们应该始终替换它而不是修改它,直接修改状态不能引发视图更新
6.3修改对象状态
对于对象类型的状态变量,应该始终给set方法一个全新的对象来进行修改
import React from "react";
function App() {
const [form, setForm] = React.useState({
name : 'jack'
})
const handleChageName=() => {
setForm({
...form,
name : 'john'
})
}
return (
<div>
<button onClick={()=>handleChageName()}>{form.name}</button>
</div>
)
}
export default App;
7.组建的基础样式处理
React组件基础的样式控制有两种方式,行内样式和class类名控制
import React from "react";
function App() {
return (
<div>
<div style={{color:'red'}}> this is div</div>
</div>
)
}
export default App;
index.css
.foo{
color: red;
}
App.js
import './index.css';
function App() {
return (
<div>
<div className="foo"> this is div</div>
</div>
)
}
export default App;
8.B站评论案例
- 渲染评论列表
- 删除评论实现
- 渲染导航Tab和高亮实现
- 评论列表排序功能实现
8.1基础项目
App.js
import './App.scss'
import avatar from './images/bozai.png'
/**
* 评论列表的渲染和操作
*
* 1. 根据状态渲染评论列表
* 2. 删除评论
*/
// 评论列表数据
const defaultList = [
{
// 评论id
rpid: 3,
// 用户信息
user: {
uid: '13258165',
avatar: '',
uname: '周杰伦',
},
// 评论内容
content: '哎哟,不错哦',
// 评论时间
ctime: '10-18 08:15',
like: 88,
},
{
rpid: 2,
user: {
uid: '36080105',
avatar: '',
uname: '许嵩',
},
content: '我寻你千百度 日出到迟暮',
ctime: '11-13 11:29',
like: 88,
},
{
rpid: 1,
user: {
uid: '30009257',
avatar,
uname: '黑马前端',
},
content: '学前端就来黑马',
ctime: '10-19 09:00',
like: 66,
},
]
// 当前登录用户信息
const user = {
// 用户id
uid: '30009257',
// 用户头像
avatar,
// 用户昵称
uname: '黑马前端',
}
/**
* 导航 Tab 的渲染和操作
*
* 1. 渲染导航 Tab 和高亮
* 2. 评论列表排序
* 最热 => 喜欢数量降序
* 最新 => 创建时间降序
*/
// 导航 Tab 数组
const tabs = [
{ type: 'hot', text: '最热' },
{ type: 'time', text: '最新' },
]
const App = () => {
return (
<div className="app">
{/* 导航 Tab */}
<div className="reply-navigation">
<ul className="nav-bar">
<li className="nav-title">
<span className="nav-title-text">评论</span>
{/* 评论数量 */}
<span className="total-reply">{10}</span>
</li>
<li className="nav-sort">
{/* 高亮类名: active */}
<span className='nav-item'>最新</span>
<span className='nav-item'>最热</span>
</li>
</ul>
</div>
<div className="reply-wrap">
{/* 发表评论 */}
<div className="box-normal">
{/* 当前用户头像 */}
<div className="reply-box-avatar">
<div className="bili-avatar">
<img className="bili-avatar-img" src={avatar} alt="用户头像" />
</div>
</div>
<div className="reply-box-wrap">
{/* 评论框 */}
<textarea
className="reply-box-textarea"
placeholder="发一条友善的评论"
/>
{/* 发布按钮 */}
<div className="reply-box-send">
<div className="send-text">发布</div>
</div>
</div>
</div>
{/* 评论列表 */}
<div className="reply-list">
{/* 评论项 */}
<div className="reply-item">
{/* 头像 */}
<div className="root-reply-avatar">
<div className="bili-avatar">
<img
className="bili-avatar-img"
alt=""
/>
</div>
</div>
<div className="content-wrap">
{/* 用户名 */}
<div className="user-info">
<div className="user-name">jack</div>
</div>
{/* 评论内容 */}
<div className="root-reply">
<span className="reply-content">这是一条评论回复</span>
<div className="reply-info">
{/* 评论时间 */}
<span className="reply-time">{'2023-11-11'}</span>
{/* 评论数量 */}
<span className="reply-time">点赞数:{100}</span>
<span className="delete-btn">
删除
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
export default App
App.scss
.app {
width: 80%;
margin: 50px auto;
}
.reply-navigation {
margin-bottom: 22px;
.nav-bar {
display: flex;
align-items: center;
margin: 0;
padding: 0;
list-style: none;
.nav-title {
display: flex;
align-items: center;
width: 114px;
font-size: 20px;
.nav-title-text {
color: #18191c;
font-weight: 500;
}
.total-reply {
margin: 0 36px 0 6px;
color: #9499a0;
font-weight: normal;
font-size: 13px;
}
}
.nav-sort {
display: flex;
align-items: center;
color: #9499a0;
font-size: 13px;
.nav-item {
cursor: pointer;
&:hover {
color: #00aeec;
}
&:last-child::after {
display: none;
}
&::after {
content: ' ';
display: inline-block;
height: 10px;
width: 1px;
margin: -1px 12px;
background-color: #9499a0;
}
}
.nav-item.active {
color: #18191c;
}
}
}
}
.reply-wrap {
position: relative;
}
.box-normal {
display: flex;
transition: 0.2s;
.reply-box-avatar {
display: flex;
align-items: center;
justify-content: center;
width: 80px;
height: 50px;
}
.reply-box-wrap {
display: flex;
position: relative;
flex: 1;
.reply-box-textarea {
width: 100%;
height: 50px;
padding: 5px 10px;
box-sizing: border-box;
color: #181931;
font-family: inherit;
line-height: 38px;
background-color: #f1f2f3;
border: 1px solid #f1f2f3;
border-radius: 6px;
outline: none;
resize: none;
transition: 0.2s;
&::placeholder {
color: #9499a0;
font-size: 12px;
}
&:focus {
height: 60px;
background-color: #fff;
border-color: #c9ccd0;
}
}
}
.reply-box-send {
position: relative;
display: flex;
flex-basis: 86px;
align-items: center;
justify-content: center;
margin-left: 10px;
border-radius: 4px;
cursor: pointer;
transition: 0.2s;
& .send-text {
position: absolute;
z-index: 1;
color: #fff;
font-size: 16px;
}
&::after {
position: absolute;
width: 100%;
height: 100%;
background-color: #00aeec;
border-radius: 4px;
opacity: 0.5;
content: '';
}
&:hover::after {
opacity: 1;
}
}
}
.bili-avatar {
position: relative;
display: block;
width: 48px;
height: 48px;
margin: 0;
padding: 0;
border-radius: 50%;
}
.bili-avatar-img {
position: absolute;
top: 50%;
left: 50%;
display: block;
width: 48px;
height: 48px;
object-fit: cover;
border: none;
border-radius: 50%;
image-rendering: -webkit-optimize-contrast;
transform: translate(-50%, -50%);
}
// 评论列表
.reply-list {
margin-top: 14px;
}
.reply-item {
padding: 22px 0 0 80px;
.root-reply-avatar {
position: absolute;
left: 0;
display: flex;
justify-content: center;
width: 80px;
cursor: pointer;
}
.content-wrap {
position: relative;
flex: 1;
&::after {
content: ' ';
display: block;
height: 1px;
width: 100%;
margin-top: 14px;
background-color: #e3e5e7;
}
.user-info {
display: flex;
align-items: center;
margin-bottom: 4px;
.user-name {
height: 30px;
margin-right: 5px;
color: #61666d;
font-size: 13px;
line-height: 30px;
cursor: pointer;
}
}
.root-reply {
position: relative;
padding: 2px 0;
color: #181931;
font-size: 15px;
line-height: 24px;
.reply-info {
position: relative;
display: flex;
align-items: center;
margin-top: 2px;
color: #9499a0;
font-size: 13px;
.reply-time {
width: 86px;
margin-right: 20px;
}
.reply-like {
display: flex;
align-items: center;
margin-right: 19px;
.like-icon {
width: 14px;
height: 14px;
margin-right: 5px;
color: #9499a0;
background-position: -153px -25px;
&:hover {
background-position: -218px -25px;
}
}
.like-icon.liked {
background-position: -154px -89px;
}
}
.reply-dislike {
display: flex;
align-items: center;
margin-right: 19px;
.dislike-icon {
width: 16px;
height: 16px;
background-position: -153px -153px;
&:hover {
background-position: -217px -153px;
}
}
.dislike-icon.disliked {
background-position: -154px -217px;
}
}
.delete-btn {
cursor: pointer;
&:hover {
color: #00aeec;
}
}
}
}
}
}
.reply-none {
height: 64px;
margin-bottom: 80px;
color: #99a2aa;
font-size: 13px;
line-height: 64px;
text-align: center;
}
8.2完成版本
8.2.1列表渲染
{/* 评论项 */}
{
list.map((item) => (
<div key={item.user.uid} className="reply-item">
{/* 头像 */}
<div className="root-reply-avatar">
<div className="bili-avatar">
<img
className="bili-avatar-img"
alt=""
/>
</div>
</div>
<div className="content-wrap">
{/* 用户名 */}
<div className="user-info">
<div className="user-name">{item.user.uname}</div>
</div>
{/* 评论内容 */}
<div className="root-reply">
<span className="reply-content">{item.content}</span>
<div className="reply-info">
{/* 评论时间 */}
<span className="reply-time">{item.ctime}</span>
{/* 评论数量 */}
<span className="reply-time">点赞数:{item.like}</span>
<span className="delete-btn" onClick={()=>deleteItem(item.rpid)}>
删除
</span>
</div>
</div>
</div>
</div>
))
8.2.2 删除评论
const [list, setList] = useState(defaultList)
const deleteItem = (rpid) => {
setList(list.filter(item => item.rpid !== rpid));
}
8.3.2渲染导航Tab和高亮显示
<li className="nav-sort">
{/* 高亮类名: active */}
{tabs.map((tab) => (
<span
key={tab.type}
className={`nav-item ${tab.type === activeTabType ? 'active' : ''}`}
onClick={() => tabChange(tab.type)}>{tab.text}</span>
))}
{/*<span className='nav-item' onClick={()=>tabChange('time')}>最热</span>*/}
</li>
const [activeTabType, setActiveTabType] = useState(tabs[0].type); // 初始激活第一个 tab
const tabChange = (type) => {
setActiveTabType(type);
};
8.3.4评论列表排序
安装lodash库
npm install lodash
const [list, setList] = useState(defaultList)
useEffect(() => {
// 根据点赞数量排序(热度排序)
const sortedComments = _.orderBy(list, 'like', 'desc')
setList(sortedComments);
}, []); // 空依赖数组表示这个 effect 只在组件挂载和卸载时运行一次
const deleteItem = (rpid) => {
setList(list.filter(item => item.rpid !== rpid));
}
const [activeTabType, setActiveTabType] = useState(tabs[0].type); // 初始激活第一个 tab
const tabChange = (type) => {
setActiveTabType(type);
if (type === 'hot') {
setList(_.orderBy(list, 'like', 'desc'));
} else if (type === 'time') {
setList(_.orderBy(list, 'ctime', 'asc'));
}
};
8.3.5 样式书写优化
npm install classnames
className={classNames('nav-item', {active: tab.type === activeTabType})}
9.React表单控制
9.1受控绑定
概念:使用React组件的状态(useState)控制表单状态
import {useState} from "react";
function App() {
const [value, setValue] = useState('');
return (
<input type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
>
</input>
);
}
export default App;
9.2非受控绑定
概念:通过DOM的方式获取表单的输入数据
import {useRef} from "react";
function App() {
var inputRef = useRef(null);
const onChange = () => {
console.log(inputRef.current.value)
}
return (
<input type="text"
ref={inputRef}
onChange={onChange}>
</input>
);
}
export default App;
10.案例-B站评论案例
1.输入评论内容,并发布评论
1)进行表单受控绑定
<div className="reply-box-wrap">
{/* 评论框 */}
<textarea
className="reply-box-textarea"
placeholder="发一条友善的评论"
value = {textValue}
onChange={(e)=>setTextValue(e.target.value)}
/>
{/* 发布按钮 */}
<div className="reply-box-send">
<div className="send-text" onClick={()=>sendComment(textValue)}>发布</div>
</div>
</div>
2)点击“发布”触发发布评论
const [textValue, setTextValue] = useState('');
const sendComment = () => {
setList([
...list,
{
// 评论id
rpid: 3,
// 用户信息
user: user,
// 评论内容
content: textValue,
// 评论时间
ctime: new Date().toLocaleString(),
like: 88,
}
])
setTextValue('');
}
3)非受控绑定输入框
<div className="reply-box-wrap">
{/* 评论框 */}
<textarea
className="reply-box-textarea"
placeholder="发一条友善的评论"
value = {textValue}
ref={inputRef}
onChange={(e)=>setTextValue(e.target.value)}
/>
{/* 发布按钮 */}
<div className="reply-box-send">
<div className="send-text" onClick={()=>sendComment(textValue)}>发布</div>
</div>
</div>
4)光标聚焦
const sendComment = () => {
setList([
...list,
{
// 评论id
rpid: 3,
// 用户信息
user: user,
// 评论内容
content: textValue,
// 评论时间
ctime: new Date().toLocaleString(),
like: 88,
}
])
setTextValue('');
inputRef.current.focus();
}
2.id处理和时间处理(uuid和day.js)
npm install dayjs --save
npm install uuid --save
具体代码实现
const sendComment = () => {
setList([
...list,
{
// 评论id
rpid: uuidV4(),
// 用户信息
user: user,
// 评论内容
content: textValue,
// 评论时间
ctime: dayjs(new Date()).format('MM-DD hh:mm'),
like: 88,
}
])
setTextValue('');
inputRef.current.focus();
}
11.React组件通信
概念:组件通信就是组件之间的数据传递,根据组件嵌套关系的不同,有不同的通信手段和方法
- A-B 父子通信
- B-C 兄弟通信
- A-E 跨层通信
11.1 父子通信-父传子
11.1.1 基础实现
实现步骤:
1.父组件传递数据-在子组件标签上绑定属性
2.子组件接收数据-子组件通过props参数接受数据
function Son(props) {
return (<div>{props.name}</div>)
}
function App() {
const name = 'react'
return (
<div><Son name={name} /></div>
);
}
export default App;
11.1.2props说明
props可以传递任意的合法数据,比如数字、字符串、布尔值、数组、对象、函数、JSX
props是只读对象
子组件只能读取props中的数据,不能直接进行修改,父组件的数据只能由父组件修改
11.1.3特殊的prop-chilren
场景:当我们把内容嵌套在组件的标签内部时,组件会自动在名为children的prop属性中接受该内容
function Son(props) {
return <div>{props.children}</div>
}
function App() {
const name = 'react'
return (
<Son>
<span>this is span</span>
</Son>
);
}
export default App;
11.2 父子通信-子传父
核心思想:子组件中调用父组件中的参数并传递函数
function Son({ onGetMsg}) {
const msg = "this is son msg";
return (
<div>
<button onClick={() => onGetMsg(msg)}>send</button>
</div>
)
}
function App() {
const getMsg = (msg) => {
console.log(msg)
}
return (
<div>
<Son onGetMsg={getMsg}></Son>
</div>
);
}
export default App;
11.3兄弟组件通信
实现思路:
借助状态提升机制,通过共同的父组件进行相抵之间的数据传递
import {useState} from "react";
function A({onGetAName}) {
const mag = "this is A name";
return (
<div>
this is A,
<button onClick={() => onGetAName(mag)}>send</button>
</div>
);
}
function B({name}) {
return <div>this is B,{name}</div>
}
function App() {
const [name, setName] = useState("");
const getAName = (name) => {
setName(name);
}
return (
<div>
<A onGetAName={getAName}></A>
<B name={name}></B>
</div>
);
}
export default App;
11.4 跨层组件通信
实现步骤:
1.使用createContext方法创建一个上下文对象Ctx
2.在顶层组件{App}中通过Ctx.Provider组件提供数据
3.在底层组件{B}中通过useContext钩子函数获取消费消息
import {createContext, useContext} from "react";
var MsgContext = createContext();
function A() {
const msg = "this is A Component";
return (
<div>
{msg}.
<B/>
</div>
);
}
function B() {
const msg = useContext(MsgContext);
return <div>this is B,{msg}</div>
}
function App() {
const msg = "this is App Component";
return (
<div>
<MsgContext.Provider value={msg}>
<A/>
</MsgContext.Provider>
</div>
);
}
export default App;
12.React副作用管理-useEffect
12.1概念理解
useEffect是一个React Hook函数,用于在React组件中创建不是由时间引起而是由渲染本身引起的操作(副作用),比如发送AJAX请求,更改DOM等等
说明:上面的组件中没有发生任何的用户事件,组件渲染完毕之后就需要跟服务器要数据,整个过程属于“只渲染引起的操作”
12.2基础使用
需求:在组建渲染完毕之后,立刻从服务器端获取数据并展示在页面中
useEffect(() => { }, [])
说明:
- 参数1是一个函数,可以把它叫做副作用函数,在函数内部可以放置要执行的操作
- 参数2是一个数组(可选参),在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行,当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次
接口地址:http://geek.itheima.net/v1_0/channels
1)安装axios
npm install axios
2)发送请求
const [list, setList] = useState([]);
useEffect(() => {
const getData = async () => {
const res = await axios.get("http://geek.itheima.net/v1_0/channels");
console.log(res.data.data.channels)
setList(res.data.data.channels)
}
getData();
}, []);
3)展示数据
import axios from "axios";
import {useEffect, useState} from "react";
function App() {
const [list, setList] = useState([]);
useEffect(() => {
const getData = async () => {
const res = await axios.get("http://geek.itheima.net/v1_0/channels");
console.log(res.data.data.channels)
setList(res.data.data.channels)
}
getData();
}, []);
return (
<div>
<ul>
{list.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
);
}
export default App;
12.3useEffect依赖说明
useEffect副作用函数执行时机存在多种情况,根据传入依赖项的不同,会有不同的执行表现
依赖项 | 副作用功函数的执行时机 |
---|---|
没有依赖项 | 组件初始渲染 + 组件更新时执行 |
空数组依赖 | 只在初始渲染时执行一次 |
添加特定依赖项 | 组件初始渲染 + 依赖项变化时执行 |
12.4清除副作用
概念:在useEffect中编写的由渲染本身引起的对接组件外部的操作,社区也经常把它叫做副作用函数,比如在useEffect中开启一个定时器,在组件卸载时把这个定时器清除掉,这个过程就是清楚副作用。
useEffect(()=>{
return ()=>{
// 清除副作用
}
},[])
说明:清除副作用函数最常见的执行时机是在组件卸载时自动执行
import {useEffect, useState} from "react";
function Son() {
useEffect(() => {
const timer = setInterval(() => {
console.log("我是子组件的定时器")
}, 1000)
return () => {
clearInterval(timer)
console.log("子组件的定时器被销毁了")
}
}, []);
return (<div>我是子组件</div>)
}
function App() {
const [show, setShow] = useState(true)
return (
<div>
{show && <Son/>}
<button onClick={() => setShow(false)}>卸载组件</button>
</div>
);
}
export default App;
13.自定义Hook
13.1自定义Hook实现
概念:自定义Hook是以use大头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用
封装自定义hook通用思路:
1.声明一个以use打头的函数
2.在函数体内封装可复用的逻辑(只要是可复用的逻辑)
3.把组件中用到的状态或者回调return出去(以对象或者数组)
4.在哪个组件中要用这个逻辑,就执行这个函数,结构出来状态和回调进行使用
import {useState} from "react";
function useToggle() {
const [value, setValue] = useState(true);
const toggle = () => setValue(!value);
return {
value, toggle
};
}
function App() {
const {value, toggle} = useToggle();
return (
<div>
{value && <div>this is div</div>}
<button onClick={toggle}>toggle</button>
</div>
);
}
export default App;
13.2React Hooks使用规则
1.只能在组件或者其他自定义Hook函数中调用
2.只能在组件的顶层调用,不能嵌套在if、for、其它函数中
14.案例-优化B站评论案例
14.1使用接口的方式获取评论列表并渲染
1)使用json-server工具模拟接口服务,通过axios发送请求
下载json-server到当前项目
npm i json-server -D
在项目文件夹下创建db.json文件
{
"list": [
{
"rpid": 3,
"user": {
"uid": "13258165",
"avatar": "http://toutiao.itheima.net/resources/images/98.jpg",
"uname": "周杰伦"
},
"content": "哎哟,不错哦",
"ctime": "10-18 08: 15",
"like": 126
},
{
"rpid": 2,
"user": {
"uid": "36080105",
"avatar": "http://toutiao.itheima.net/resources/images/98.jpg",
"uname": "许嵩"
},
"content": "我寻你千百度 日出到迟暮",
"ctime": "11-13 11: 29",
"like": 88
},
{
"rpid": 1,
"user": {
"uid": "30009257",
"avatar": "http://toutiao.itheima.net/resources/images/98.jpg",
"uname": "黑马前端"
},
"content": "学前端就来黑马",
"ctime": "10-19 09: 00",
"like": 66
}
]
}
在package.json下添加启动命令
"serve": "json-server db.json --port 3004"
2)使用useEffect调用接口获取数据
封装Hook调用接口函数
function useGetList() {
const [list, setList] = useState([]);
useEffect(() => {
async function getList() {
const res = await axios.get('http://localhost:3004/list');
setList(res.data);
}
getList()
}, []);
return {
list,
setList
}
}
调用Hook函数
const {list, setList} = useGetList();
14.2把评论中的每一项抽象成一个独立的组件实现渲染
1)封装组件
function Item({item, onDel}) {
return (
<div key={item.rpid} className="reply-item">
{/* 头像 */}
<div className="root-reply-avatar">
<div className="bili-avatar">
<img
className="bili-avatar-img"
alt=""
/>
</div>
</div>
<div className="content-wrap">
{/* 用户名 */}
<div className="user-info">
<div className="user-name">{item.user.uname}</div>
</div>
{/* 评论内容 */}
<div className="root-reply">
<span className="reply-content">{item.content}</span>
<div className="reply-info">
{/* 评论时间 */}
<span className="reply-time">{item.ctime}</span>
{/* 评论数量 */}
<span className="reply-time">点赞数:{item.like}</span>
<span className="delete-btn" onClick={() => onDel(item.rpid)}>
删除
</span>
</div>
</div>
</div>
</div>
);
}
2)调用组件
<div className="reply-list">
{/* 评论项 */}
{
list.map((item) => (
<Item key={item.id} item={item} onDel={deleteItem}/>
))
}
</div>