Hello!大家好,今天带来的是React前端JS库的学习,课程来自黑马的往期课程,具体连接地址我也没有找到,大家可以广搜巡查一下,但是总体来说,这套课程教学质量非常高,每个知识点都有一个小案例,最后有一个总的美团外卖案例教学,大家可以看看我这篇文章,如果能够帮到你们,还请多多点赞o( ̄▽ ̄)d支持支持🌹,如果文章中有错误的或者是遗漏信息,可以在评论区指出或者是与我私信。我看到了消息,一定会及时更正过来∠(°ゝ°)。话不多说,直接开学💪⛽️!
本篇教学已完结,具体可查看教程:
1. 一天搞定React(1)——React安装与配置
2. 一天搞定React(2)——JSX语法
3. 一天搞定React(3)——Hoots组件
4. 一天搞定React(4)——Redux
5. 一天搞定Recat(5)——ReactRouter(上)
6. 一天搞定React(5)——ReactRouter(下)
文章目录
- 组件学习
- 创建组件
- 组件的状态
- 修改状态
- 【案例】B站评论
- classNames优化
- 状态操作表单元素
- useRef
- 组件通讯
- 父子组件通讯
- 父传子通讯
- 简化props
- 子到父通讯
- 非父子通讯
- 兄弟关系
- 后代关系
- useEffet的使用
- useEffect扩展
- useEffect应用——发送请求
- Json-server
- 使用axios
- Hooks
组件学习
创建组件
在src
目录下新建一个App.js
文件,这个App.js就是我们创建的新的组件。
其实组件就是一个函数,所以我们只需要创建函数就能完成组件的创建。
在App.js文件中编写以下内容:
-
创建一个函数
const 组件名 =()=>{return(<div>App组件</div>) }
-
导出组件
export default 组件名
-
回到
index.js
文件,导入App组件文件路径import 组件名 from '文件路径'
-
在
index.js
文件中使用组件root.render(<div><组件库 /></div> );
组件的状态
状态(state):是可以让页面内容发生变化的数据。
使用方法:useState()
函数,如果需要修改状态就调用setCount(状态最新值)
函数。
const [count, setCount] = useState(默认值)
其中第一个元素是当前状态的值,第二个元素是更新该状态的函数。
我们来写一个计数器来更好得到理解state:
import { uesState } from 'react'const App =()=>{const [count, setCount] = uesState(10)return(<div><h1>计数器:{count} </h1><button onClick={()=> setCount(count+1)}>+1</button><button onClick={()=> setCount(count-1)}>-1</button></div>)
}
export default App
修改状态
规则:不要直接修改当前状态的值,应该创建新值。
注意:如果没有遵循该规则,会导致报错或组件无法重新渲染
如图:
在数组类型也是符合这种规则,如图:
扩展:
...list
这个是表示之前的元素setList([...list,‘新的内容'])
【案例】B站评论
我们要实现下图👇案例
具体内容要求是有:
- 使用组件搭建结构
- 导航Tab的渲染和操作
- 评论列表的渲染和操作
安装Sass包
在这个项目当中我们需要使用到Sass更好的绘制样式形状,所以会用到Sass。如果你还不会Sass的话,可以跟我这之前的写过的一篇文章进行学习,相信你很快就能上手并熟练运用的💪⛽️
文章:一天搞定前端必备开发技能 (2)——less和Sass预处理器的使用_项目里嵌入 less 预处理器-CSDN博客
安装命令:
npm i sass
之后再src文件夹中创建一个
app.scss
文件用来编写sass样式。
之后我们在App.js文件导入scss文件和图片
导入图片
使用本地图片,需要手动导入。
❌不能像html一样直接在
src
导入图片路径import 图片名称 from "图片路径"const App =()=>{return(//使用图片<img src={"图片名称"} alt="图片备注"></img>) } export default App
在这里我准备好了模版结构和样式给大家,大家直接复制粘贴到App.js文件当中即可;
🔗链接:B站评论案例笔记.md
注意⚠️:图片没有提供给大家,在代码中如果涉及到图片的请自行修改🌺。
组件结构搭建完成之后,我们就可以开始编写交互逻辑啦。
一共要实现以下👇三个功能:
- 根据状态渲染评论列表
//评论列表数据
const defaultList = [{//评论idrpid: 3,//用户信息user: {uid: "13258165",avatar: "https://未知信息",uname: "周杰伦",},//评论内容content: "哎哟,不错哦",//评论时间ctime: "10-1808:15",//喜欢数量like: 98,//0:未表态 1:喜欢 2:不喜欢action: 0,},];
使用useState
来修改状态
const App=()=>{const[list,setList] = useState(defaultList)
}
列表渲染
<div classNaem="reply-list">{/*评论区*/}{list.map(item=>{return 列表项目})}
</div>
⚠️不要忘记了给每一个列表项添加key属性
然后将这些评论编程动态的:
-
评论头像
-
评论名称
-
评论事件
-
喜欢数量
然后评论列表的渲染就基本完成了
- 删除评论
删除按钮的特性就是只有自己发表的评论才能够删除的,那么如何来实现呢?那就是使咱们登录用户的ID来索引判断是否是自己发表的评论。
{user.uid === item.user.uid &&() <span className="delete-bt">删除</span>)}
创建删除评论函数
//先给删除按钮绑定一个点击事件
onClick={onDelete(item.rpid))//删除评论
const onDelete=rpid=>{console.log(rpid)
}
最后用数组的过滤方法删除掉渲染
//删除评论
const onDelete=rpid=>{setList(list.filter(item=> item.rpid !== rpid))//如果要删除数组中的元素,需要调用 filter方法,并且一定要调用setList
}
- 喜欢和不喜欢
通过传入的action
来判断是否是喜欢还是不喜欢。
绑定点击事件
onClick={() =>onLike(item.rpid)}//创建一个喜欢的函数
const onLike=rpid=>{console.log(rpid)
}
高亮的效果显示其实就是一个取反的操作;喜欢的数量改变,还是一样先拿到action,如果是喜欢的话,就让item.like进行加减。
不喜欢渲染也是一样的,这里我就不过多赘述啦!
classNames优化
项目开发中,当多个类名都是动态的,手动处理会变得非常困难。
如果有多个类别都是动态的,那这个时候再来进行手动处理呢,会变得非常的困难,比如咱们来看一个例子,这边是我们经常用的一种类别处理的方式。但如果再有一个。也是动态的,你可能需要。再写一个三元表达式。3个第4个呢,再手动。那有没有更好的方式呢?
<button className={disabled ?'btnbtn-disabled':'btn'}></button>
今天我们就来学习使用classnames包
来优化类名处理。
-
安装包
npm i classnames
-
导包
import classNames from 'classnames'
使用方法:
咱们直接上案例来进行学习
import classNames from 'classnames'
const App =()=>{// 是否禁用const [disabled,setDisabled] = useState(false)// 大小const [size,setSize] =useState('small')return(<div><button>按钮</button><hr/><div>操作:<button onClick={()=> setDisabled(true)}>禁用</button><button onClick={()=> setSize('small')}>变小</button><button onClick={()=> setSize('large')}>变大</button></div></div>)
}
export default App
以上👆就是我们会对这个按钮,给它来去加一些相应的样式,那并且这边,我还准备了两个状态,一个表示是否禁用一个。是按钮的大小,然后接下来呢,咱们还有分别有3个按钮进行控制。
-
如果类名一直都有的,就在函数参数内写好
<button className={classNames('一直都用的class')}>按钮</button>
-
使用逻辑运算(适合处理单个类名)
//语法模版 <button className={className('一直都用的class',条件1 && ‘样式1’)}>按钮</button>
//联系案例 // 还需要联系上面的useState状态来判断const [disabled,setDisabled] = useState(false)<button className={classNames('btn',disabled && 'btn-disabled')}>按钮</button>
这里
classNames
函数会根据disabled
的值来决定是否添加'btn-disabled'
这个类名。如果disabled
为true
,className
将会是'btn btn-disabled'
;如果disabled
为false
,className
将会是'btn'
。 -
使用对象语法(适合处理多个类名)
//语法模版 <button className={className('一直都用的class',{'样式1':条件1,'样式2':条件2,'样式3':条件3, })}>按钮</button>
//联系案例// 是否禁用const [disabled,setDisabled] = useState(false)// 大小const [size,setSize] =useState('small')const [disabled,setDisabled] = useState(false)<button className={className('btn',{})}>按钮</button>
注意⚠️:
useState(false)
表示初始状态disabled
的值被设置为false
。setDisabled
是一个函数,你可以在组件中调用它来更新disabled
的状态。所以,初始时disabled
的取值是false
。
完整代码:
import {useState} from 'react'
import classNames from 'classnames'
// import 'App.css'; // 导入CSS模块
const App =()=>{// 是否禁用const [disabled,setDisabled] = useState(false)// 大小const [size,setSize] =useState('small')return(<div><button className={classNames('btn',{'btn-disabled':disabled,'btn-small':size === 'small','btn-large':size === 'large',})}>按钮</button><hr/><div>操作:<button onClick={()=> setDisabled(true)}>禁用</button><button onClick={()=> setSize('small')}>变小</button><button onClick={()=> setSize('large')}>变大</button></div></div>)
}
export default App
状态操作表单元素
步骤:
-
准备一个状态
cosnt [value,setValeue] = useState('')
-
实现数据到是视图的绑定,使用
value
值绑定状态值 -
实现视图到数据的绑定,使用
onChange
属性设置状态<input value={value} onChange={e => setValue(e.target.value)}
e.target
:拿到dom对象
-
调用
setValue()
函数来更新状态
案例:绑定复选框
import { useState } from "react";const App = () => {const [checked, setChecked] = useState(false);return (<div><inputtype="checkbox"checked={checked}onChange={(e) => setChecked(e.target.checked)}/>{checked ? "选中了" : "未选中"}</div>);
};
export default App;
注意⚠️:
value
和onChange
的值需要同时出现,不然没法实现绑定事件.
这种模式在React中有一个专业的术语:受控组件
.所谓的受控组件就是收到React状态控制的表单元素.
useRef
在React组件中操作DOM,需要使用useRef,分为两步:
-
创建一个
ref
对象const 对象名 = useRef(null)
-
与JSX绑定
<input ref={对象名}/>
-
根据业务进行DOM操作:通过
对象名.current.DOM属性
拿到DOM对象
案例:
import { useRef } from "react";const inputRef = useRef(null)<input ref={inputRef}/><button onClick={()=>alert(inputRef.current.value)}>获取文本框的值</button>
组件通讯
使用场景:一个组件需要使用另一个组件的数据。
根据组件之间的层级关系,常见的React组件通讯分为3种:
- 父子组件通讯
- 非父子组件通信
- 状态管理工具
父子组件通讯
父传子通讯
父组件提供数据,通过props传递给子组件使用。
作用:给组件传递数据,是React组件通讯的基础。
使用步骤:
-
传递props:在组件标签上添加属性
<组件名 定义传递属性="属性值"/>
-
接受props:通过参数拿到
const 组件名 = props =>{return(<标签 属性={props.定义传递属性}/>) }
案例:
const Study = props=>{return (<div style={{backgroundColor:'red' , width:props.w, height:props.h }}></div>)
}
//使用组件
const App = () => {
return (<div><Study w={100} h={200} />{/* <Study w={"100px"} h={"200px"} /> */}</div>);
};
export default App;
在React中,属性的传递方式有两种:字符串和JavaScript表达式。在上面的例子中,
<Study w="100px" h="200px" />
和<Study w=100 h=200 />
表现的行为是不同的:
- 字符串形式 (
<Study w="100px" h="200px" />
):
- 这里的
w
和h
属性被传递为字符串。即使它们看起来像是数字后面跟着单位(例如"100px"
),它们在JavaScript中被当作字符串处理。- 这意味着在组件内部,
props.w
和props.h
将会是字符串类型的值,包含单位。- JavaScript表达式形式 (
<Study w=100 h=200 />
):
- 这里的
w
和h
属性直接作为JavaScript表达式传递,没有引号。这意味着它们将按照它们在JSX中声明的类型传递给组件。在组件中如果期望
props.w
和props.h
是带有单位的CSS尺寸值。最好还是在设置样式之前将数字转换为带有单位的字符串。例如,在组件内部添加逻辑来添加单位:
const Study = props => {const width = `${props.w}px`; // 假设props.w是数字,这里添加了'px'单位const height = `${props.h}px`; // 同上return (<div style={{backgroundColor: 'red', width, height }}></div>); };
在这个例子中,无论
props.w
和props.h
是数字还是字符串,我们都确保它们被转换为带有单位的字符串,以便正确地应用CSS样式。注意⚠️:这里运用到了模板字符串(也称为模板字面量)的语法。模板字符串允许你嵌入表达式到字符串字面量中,用
${}
包裹表达式,然后通过插值表达式的值。
简化props
我们还可以对props对象进行简化,由于props是一个对象,我们这里可以直接通过结构的方式直接拿到传过来的值。然后就能删掉props.
const Study = ({w,h})=>{return (<div style={{backgroundColor:'red' , width:w, height:h }}></div>)
}
//使用组件
const App = () => {
return (<div><Study w={100} h={200} /></div>);
};
export default App;
在实际开发中我们推荐使用简化方式,另外还可以在结构里面添加一个默认值。
const Study = ({w=10,h=20})=>{return (<div style={{backgroundColor:'red' , width:w, height:h }}></div>)
}
//使用组件
const App = () => {
return (<div><Study w={100} h={200} />对比,上面一个是修改了默认值,下面一个是直接默认值<Study/></div>);
};
export default App;
子到父通讯
语法模版:
-
父组件准备修改数据的函数,传递给子部件
//父组件 const 修改数据函数名 = 参数=>{} <标签名 修改数据函数名={修改数据函数名}></标签名> //将修改数据函数传递给子组件
-
子组件调用函数,将数据作为参数回到父组件
//子组件 const 组件名 =({参数,修改数据函数名})=>{return <标签名 onClick ={()=>修改数据函数名(值)}></标签名> } //子组件调用函数
案例:
App.css文件:
.container {display: flex; /* 设置为Flex容器 */align-items: center; /* 垂直居中对齐 */
}
.yes { background-color: #c0e2f0;width: 100px;height: 30px;border: 1px solid black;margin:5px;text-align: center;
}
.no {background-color: #f6c8c8;
}
App.js文件:
import { useState } from "react";
import classNames from "classnames";
import "./App.css";
// 子组件
const Son = ({ id, num,change }) => {//子组件调用函数changereturn (<div className="container"><div className={classNames("yes", id && "no")}>{String(id)}</div><button onClick={() => change(num)}>点击按钮变换颜色</button>{/*change(id)将数据作为参数回到父组件 */}</div>);
};// 数据
const datas = [{num: 1,id: true,},{num: 2,id: true,},{num: 3,id: false,},
];// 父组件
const App = () => {const [data, setData] = useState(datas); //准备默认值const change = (num) => {//父组件准备修改数据的函数setData(data.map((item) => {if (item.num === num) {return {...item,id: !item.id,};}return item}));};return (<div>{data.map((item) => {return <Son key={item.num} num= {item.num} id={item.id} change={change} />;//遍历渲染 change={change}传递给子部件})}</div>);
};
export default App;
最后实现的案例效果:
我们还可以在遍历渲染的时候再次优化代码:
<div>{data.map((item) => {// return <Son key={item.num} num= {item.num} id={item.id} change={change} />;return <Son key={item.num} {...item} change={change} />;//{...item}表示数据中的其他属性分别传递给子组件})}</div>
非父子通讯
兄弟关系
根据组件之间的层级关系,常见的有两种情况:
- 找到父组件,提供要共享的数据
- 通过父到子通讯,来展示好友名称
- 通过子到父通讯,来修改选中的好友
在React中有专门的一个术语来形容:状态提升。
如果两个兄弟组件要通讯,就把共享数据提升到公共父组件中
后代关系
React的Context API
提供了一种方式,允许你共享那些对于组件树中许多组件都必不可少的数据,例如用户偏好、地区设置、主题等,而不必通过每一层组件手动传递props。
Context(上下文)
:范围,无视组件层级关系,
跨组件通讯。
-
创建Context对象
const 对象名 = createContext()
-
划定范围,提供共享数据
<对象名.Provider value={共享数据}>父组件 </对象名.Provider>
-
范围内的组件,获取共享数据
const 共享数据 = useContext(对象名)
案例:共享消息
import React, { createContext, useState, useContext } from 'react';// 创建Context对象
const MessageContext = createContext();// 创建Provider组件
const MessageProvider = ({ children }) => {const [message, setMessage] = useState('Hello, World!');// 更新消息的函数const updateMessage = (newMessage) => {setMessage(newMessage);};return (<MessageContext.Provider value={{ message, updateMessage }}>{children}</MessageContext.Provider>);
};// 创建子组件
const MessageDisplay = () => {const { message, updateMessage } = useContext(MessageContext);return (<div><p>{message}</p><button onClick={() => updateMessage('New Message')}>Change Message</button></div>);
};// 创建另一个子组件
const AnotherComponent = () => {const { message } = useContext(MessageContext);return (<div><h2>Another Component</h2><p>Message from context: {message}</p></div>);
};// 应用的顶层组件
const App = () => {return (<MessageProvider><div><MessageDisplay /><AnotherComponent /></div></MessageProvider>);
};export default App;
代码解释:
- 创建Context: 使用
createContext
创建一个MessageContext
。 - 创建Provider:
MessageProvider
组件使用useState
钩子来管理消息状态,并提供了一个updateMessage
函数来更新消息。它通过MessageContext.Provider
将这些值提供给子组件。 - 创建子组件:
MessageDisplay
组件使用useContext
钩子从MessageContext
获取消息和更新函数,并显示消息。它还提供了一个按钮来更改消息。AnotherComponent
组件也使用useContext
钩子从MessageContext
获取消息,并显示它。
- 应用的顶层组件:
App
组件使用MessageProvider
来包裹MessageDisplay
和AnotherComponent
,确保它们可以访问到Context中的数据。
useEffet的使用
useEffect的作用:在组件生命周期的三个阶段(挂载、更新、卸载),执行网络请求、浏览器API等操作。这些操作,也叫:副作用😭。
语法:useEffect(Effect函数,依赖项数组)
Effect函数
:副作用代码依赖项数组
:控制Effect函数的执行时机(可选填)【一般在使用动态数据的时候使用,在更新时用的最多】
生命周期三个阶段:
-
挂载时:
useEfect(()=>{//挂载操作代码 },[])
-
更新时:
useEffect(()=>{//更新操作代码 },[依赖项数组])
注意⚠️:更新时调用方式会在组件挂载以及更新时都会执行。
-
卸载时:
useEffect(()=>{return()=>{//卸载操作代码} },[])
案例:计数器组件
这个组件将展示一个计数器,每次点击按钮时计数器会增加。同时,组件会在挂载时打印一条消息,在更新时打印另一条消息,并在卸载时清理资源。
import React, { useState, useEffect } from 'react';function Counter() {const [count, setCount] = useState(0);// 挂载时执行的副作用useEffect(() => {console.log('组件挂载');return () => {console.log('组件卸载');};}, []); // 空依赖项数组,仅在挂载时执行// 更新时执行的副作用useEffect(() => {console.log(`计数器更新为:${count}`);}, [count]); // 依赖项数组包含 count,每当 count 更新时执行return (<div><p>You clicked {count} times</p><button onClick={() => setCount(count + 1)}>Click me</button></div>); }export default Counter;
代码解释:
- 状态初始化: 使用
useState
钩子初始化计数器状态count
。 - 挂载时的副作用: 第一个
useEffect
没有依赖项数组,因此它只在组件挂载时执行一次。它在控制台中打印一条消息,并在组件卸载时执行返回的函数,打印另一条消息。 - 更新时的副作用: 第二个
useEffect
依赖于count
。每当count
更新时,这个useEffect
都会重新运行,并在控制台中打印新的计数器值。 - 渲染UI: 组件渲染一个段落和一个按钮。按钮的点击事件处理器会调用
setCount
函数,从而更新count
状态。
这个示例展示了
useEffect
如何在组件的不同生命周期阶段执行代码,以及如何通过依赖项数组控制副作用的执行。 - 状态初始化: 使用
useEffect扩展
推荐:一个useEffect负责一个完整功能
useEffect(()=>{//更新操作代码(这里直接用更新来代替挂载)return()=>{//卸载操作代码}
},[依赖项数组])
依赖项说明:什么样的数据才能说明是依赖项呢?
指定依赖项的原则:Effect函数中用到的,并且是可变的值。(例如:props/state/组件中创建的变量等)
以下是一个简单的例子,演示了如何使用 useEffect
来实现一个组件的挂载和卸载操作:
import React, { useState, useEffect } from 'react';function Clock() {// 组件状态,用于显示时间const [time, setTime] = useState(new Date().toLocaleTimeString());// 使用 useEffect 钩子来处理副作用useEffect(() => {// 定义一个更新时间的函数const updateTime = () => {setTime(new Date().toLocaleTimeString());};// 设置定时器,每秒更新时间const timer = setInterval(updateTime, 1000);// 这是 effect 的清理函数,会在组件卸载时执行return () => {clearInterval(timer);};}, []); // 空的依赖项数组意味着这个 effect 只在挂载时运行一次// 渲染时显示当前时间return (<div>The time is: {time}</div>);
}export default Clock;
在这个例子中:
Clock
组件显示当前的时间,并且每秒更新一次。useState
钩子用于创建time
状态变量,初始值为当前的本地时间字符串。useEffect
钩子用于设置一个定时器,该定时器每秒调用updateTime
函数来更新时间。- 定时器通过
setInterval
创建,并在useEffect
的返回函数中清除,以确保在组件卸载时不会继续运行。 - 依赖项数组是空的
[]
,这意味着useEffect
只在组件挂载时运行一次,不会在更新时重新运行。
useEffect应用——发送请求
场景:组件初次渲染时,发送请求获取数据。
步骤:
- 调用useEffect,依赖项为空数组
- 创建新函数,在该函数上使用
async
并调用 - 发送请求,通过
await
获取到数据,然后使用
useEffect(()=>{const 函数名 = async()=>{await}函数名() //调用函数
},[])
Json-server
使用json-server
提供一个接口
使用axios
安装命令
npm i axios
useEffect(()=>{const 函数名 = async()=>{const res = await axois get('接口地址')setState(res.data) //更新到状态State里面}函数名() //调用函数
},[])
Hooks
ReactHooks是以use开头的函数,比如,useState/useEffect/useContext等。
Hooks(钩子):为组件提供不同的React特性,比如,useStateHook为组件提供状态。
使用规则:只能在组件的顶层调用,不能嵌套在if、for、其他函数中。
原理:React组件依赖Hooks的调用顺序,必须保证所有Hooks,在每次渲染时,调用顺序一致。