useState
在React中,
useState
是一个非常重要的Hook,它允许你在函数组件中添加“状态”(state)。在传统的React类组件中,我们使用this.state
来管理和更新组件的状态。然而,在函数组件中,由于它们没有实例,因此我们不能直接使用this.state
。这时,useState
Hook 就派上了用场。
在函数组件中,你可以使用useState
来声明一个新的状态变量。useState
接收一个初始状态值作为参数,并返回一个状态变量数组。数组的第一个元素是当前的状态值,第二个元素是一个用于更新状态的函数。
function ExampleComponent() { // 声明一个新的状态变量,初始值为 0 const [count, setCount] = useState(0); // 渲染一个按钮,点击时增加计数器的值 return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> );
}
状态更新是异步的
需要注意的是,React可能会将多个setState
调用合并成一个批处理更新,以提高性能。因此,你不应该依赖于setState
的立即结果来计算下一个状态。例如,以下代码可能不会按预期工作:
setCount(count + 1);
console.log(count); // 这可能仍然打印旧的count值
如果你需要根据前一个状态来计算新的状态,你可以将函数作为setCount
的参数传递,该函数接收当前的状态值并返回新的状态值:
setCount(prevCount => prevCount + 1);
总结
useState
是React的一个Hook,允许你在函数组件中添加状态。useState
接收一个初始状态值作为参数,并返回一个包含当前状态值和更新状态函数的数组。- 状态更新可能是异步的,因此你不应该依赖于
setState
的立即结果。- 如果你需要根据前一个状态来计算新的状态,可以将函数作为
setState
的参数传递。
JSX中使用JS表达式
在JSX中可以通过大括号语法}识别JavaScript中的表达式,比如常见的变量、函数调用、方法调
用等等
1.使用引号传递字符串 2.使用JavaScript变量
3.函数调用和方法调用 4.使用JavaScript对象
//列表渲染
const items = ['Apple', 'Banana', 'Cherry']; function ListItems() { return ( <ul> {items.map((item, index) => ( <li key={index}>{item}</li> ))} </ul> );
}
//渲染条件内容:
function Greeting(props) { const isLoggedIn = props.isLoggedIn; let content; if (isLoggedIn) { content = <p>Welcome back!</p>; } else { content = <p>Please log in.</p>; } return ( <div> {content} </div> );
}
-------------------------------------------------
function Greeting(props) { return ( <div> {props.isLoggedIn ? <p>Welcome back!</p> : <p>Please log in.</p>} </div> );
}
React基础事件绑定与传参
在React中,事件绑定是通过在JSX元素上设置事件处理属性来完成的。这些事件处理属性通常以on
开头,后跟事件名称(例如,onClick
、onChange
、onMouseMove
等),并且它们的值是一个函数。当特定事件在DOM元素上触发时,该函数将被调用。
import React from 'react'; class ButtonComponent extends React.Component { handleClick = () => { alert('Button was clicked!'); } render() { return ( <button onClick={this.handleClick}> Click Me </button> ); }
} export default ButtonComponent;
在上面的例子中,
ButtonComponent
类组件有一个handleClick
方法,该方法在按钮被点击时会被调用。我们通过在JSX中的button
元素上设置onClick
属性,并将handleClick
方法作为该属性的值来绑定事件。请注意,我们在
handleClick
方法前使用了箭头函数语法(handleClick = () => { ... }
),这是类属性(class fields)语法的一部分,它允许我们在类体中直接定义方法。箭头函数确保this
在handleClick
方法内部指向当前的类实例,而不是undefined
(在非箭头函数中,如果在类的方法中不使用bind
,this
通常会指向undefined
,除非在构造函数中绑定了this
)。
- 类组件中的方法(Class Component Methods):如果你不使用类属性语法,你也可以在构造函数中绑定方法:
import React from 'react'; class ButtonComponent extends React.Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } handleClick() { alert('Button was clicked!'); } render() { return ( <button onClick={this.handleClick}> Click Me </button> ); }
} export default ButtonComponent;
在这个例子中,我们在构造函数中调用了
.bind(this)
来确保this
在handleClick
方法内部指向正确的上下文。
- 使用Hooks(Function Components with Hooks):在函数组件中,你通常会使用Hooks(如
useCallback
)来处理事件绑定,以避免在每次渲染时都创建新的函数实例,这可能会导致不必要的性能问题:
import React, { useCallback } from 'react'; function ButtonComponent() { const handleClick = useCallback(() => { alert('Button was clicked!'); }, []); // 依赖项数组,因为这里不需要依赖任何props或state,所以为空数组 return ( <button onClick={handleClick}> Click Me </button> );
} export default ButtonComponent;
注意,
useCallback
的第二个参数是一个依赖项数组,它告诉React只有当这些依赖项发生变化时,才需要重新创建回调函数。在这个例子中,我们不需要任何依赖项,所以传递了一个空数组。
- 箭头函数 :在React组件的JSX中,可以直接使用箭头函数来绑定事件处理函数。但是,这种方式会在每次渲染时都创建一个新的函数实例,这可能会导致性能问题,特别是当在列表中渲染多个元素时。
function MyComponent() { const handleClick = () => { console.log('Button clicked!'); }; return ( <button onClick={handleClick}>Click Me</button> );
}
B站评论案例
-
渲染评论列表
-
删除评论实现
-
渲染导航Tab和高亮实现
-
评论列表排序功能实现
基础模版
import { useState } from 'react'
import './App.scss'
import avatar from './images/bozai.png'/*** 评论列表的渲染和操作** 1. 根据状态渲染评论列表* 2. 删除评论*/// 评论列表数据
const defaultList = [{// 评论idrpid: 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 = {// 用户iduid: '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">{/* 评论框 */}<textareaclassName="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"><imgclassName="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 {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: 76px;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;
}
完成版本
import { useState } from 'react'
import './App.scss'
import avatar from './images/bozai.png'
import orderBy from 'lodash/orderBy'/*** 评论列表的渲染和操作** 1. 根据状态渲染评论列表* 2. 删除评论*/// 评论列表数据
const defaultList = [{// 评论idrpid: 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 = {// 用户iduid: '30009257',// 用户头像avatar,// 用户昵称uname: '黑马前端',
}/*** 导航 Tab 的渲染和操作** 1. 渲染导航 Tab 和高亮* 2. 评论列表排序* 最热 => 喜欢数量降序* 最新 => 创建时间降序*/// 导航 Tab 数组
const tabs = [{ type: 'hot', text: '最热' },{ type: 'time', text: '最新' },
]const App = () => {// 导航 Tab 高亮的状态const [activeTab, setActiveTab] = useState('hot')const [list, setList] = useState(defaultList)// 删除评论const onDelete = rpid => {// 如果要删除数组中的元素,需要调用 filter 方法,并且一定要调用 setList 才能更新状态setList(list.filter(item => item.rpid !== rpid))}// tab 高亮切换const onToggle = type => {setActiveTab(type)let newListif (type === 'time') {// 按照时间降序排序// orderBy(对谁进行排序, 按照谁来排, 顺序)newList = orderBy(list, 'ctime', 'desc')} else {// 按照喜欢数量降序排序newList = orderBy(list, 'like', 'desc')}setList(newList)}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">{list.length}</span></li><li className="nav-sort">{/* 高亮类名: active */}{tabs.map(item => {return (<divkey={item.type}className={item.type === activeTab ? 'nav-item active' : 'nav-item'}onClick={() => onToggle(item.type)}>{item.text}</div>)})}</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">{/* 评论框 */}<textareaclassName="reply-box-textarea"placeholder="发一条友善的评论"/>{/* 发布按钮 */}<div className="reply-box-send"><div className="send-text">发布</div></div></div></div>{/* 评论列表 */}<div className="reply-list">{/* 评论项 */}{list.map(item => {return (<div key={item.rpid} className="reply-item">{/* 头像 */}<div className="root-reply-avatar"><div className="bili-avatar"><imgclassName="bili-avatar-img"src={item.user.avatar}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>{user.uid === item.user.uid && (<spanclassName="delete-btn"onClick={() => onDelete(item.rpid)}>删除</span>)}</div></div></div></div>)})}</div></div></div>)
}export default App