参考课程地址:
React入门到实战
React是什么?
React是一个用于构建用户界面的JavaScript库,它主要用于构建UI,很多人认为React是MVC中的V(视图)。React起源于Facebook的内部项目,用来架设Instagram的网站,并于2013年5月开源。React拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。
React有什么特点
元框架
为各种领域赋能现在的前端开发环境已经趋于工程化,我们一般使用脚手架来创建一个项目的初始环境
那么什么是脚手架呢?
在这里我们使用的脚手架就是create react app:
$ npx create-react-app react-basic
说明:
启动项目:
$ yarn start
or
$ npm start
入口文件index.js说明:
//react框架的核心包
import React from 'react'
//专门做渲染方面的包
import ReactDOM from 'react-dom'
//应用的全局样式文件
import './index.css'
// 引入根组件App
import App from './App'
// 通过调用ReactDOM的render方法渲染App根组件到id为root的dom节点上
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
//如果是react18版本
//注意把严格模式去掉,否则会影响useEffect的执行时间
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
概念:JSX是 JavaScript XML(HTML)的缩写,表示在 JS 代码中书写 HTML 结构
作用:在React中创建HTML结构(页面UI结构)
优势:
注意:JSX 并不是标准的 JS 语法,是 JS 的语法扩展,浏览器默认是不识别的,脚手架中内置的 @babel/plugin-transform-react-jsx 包,用来解析该语法
接比如下面的图片,我们写的是左边,其实编译的时候变成了右边:
语法
const name = '柴柴'
<h1>你好,我叫{name}</h1> // 你好,我叫柴柴
可以使用的表达式
特别注意
if 语句/ switch-case 语句/ 变量声明语句,这些叫做语句,不是表达式,不能出现在 {} 中!!
技巧:能够通过console.log打印出来的就是表达式
页面的构建离不开重复的列表结构,比如歌曲列表,商品列表等,我们知道vue中用的是v-for,react这边如何实现呢?
实现:使用数组的map 方法
// 来个列表
const songs = [
{ id: 1, name: '痴心绝对' },
{ id: 2, name: '像我这样的人' },
{ id: 3, name: '南山南' }
]
function App() {
return (
<div className="App">
<ul>
{
songs.map(item => <li>{item.name}</li>)
}
</ul>
</div>
)
}
export default App
作用:根据是否满足条件生成HTML结构,比如Loading效果
实现:可以使用 三元运算符
或 逻辑与(&&)运算符
// 来个布尔值
const flag = true
function App() {
return (
<div className="App">
{/* 条件渲染字符串 */}
{flag ? 'react真有趣' : 'vue真有趣'}
{/* 条件渲染标签/组件 */}
{flag ? <span>this is span</span> : null}
</div>
)
}
export default App
如果是复杂的条件渲染,我们可以把整个逻辑封装成一个函数,然后直接在{}
中执行函数即可。
例如:
行内样式
function App() {
return (
<div className="App">
<div style={{ color: 'red' }}>this is a div</div>
</div>
)
}
export default App
行内样式优化版
const styleObj = {
color:red
}
function App() {
return (
<div className="App">
<div style={ styleObj }>this is a div</div>
</div>
)
}
export default App
类名方式
.title {
font-size: 30px;
color: blue;
}
function App() {
return (
<div className="App">
<div className='title'>this is a div</div>
</div>
)
}
export default App
动态类名
import './app.css'
const showTitle = true
function App() {
return (
<div className="App">
<div className={ showTitle ? 'title' : ''}>this is a div</div>
</div>
)
}
export default App
<>>
(幽灵节点)替代()
包裹,防止bug出现React的组件概念是指将UI界面拆分成可复用的代码片段,每个片段都是一个独立的模块,可以有自己的状态和逻辑,也可以接收外部传入的数据和事件。React的组件可以用函数或类的形式来定义,也可以用JSX语法来描述组件的结构和样式。React的组件可以嵌套和组合,形成复杂的应用界面。React的组件的优点是可以提高开发效率和代码质量,也可以保证UI和数据的一致性。
使用 JS 的函数(或箭头函数)创建的组件,就叫做函数组件
接下来我们来看一个函数组件的定义和渲染的例子:
// 定义函数组件
function HelloFn () {
return <div>这是我的第一个函数组件!</div>
}
// 定义类组件
function App () {
return (
<div className="App">
{/* 渲染函数组件 */}
<HelloFn />
<HelloFn></HelloFn>
</div>
)
}
export default App
约定说明
使用 ES6 的 class 创建的组件,叫做类(class)组件
// 引入React
import React from 'react'
// 定义类组件
class HelloC extends React.Component {
render () {
return <div>这是我的第一个类组件!</div>
}
}
function App () {
return (
<div className="App">
{/* 渲染类组件 */}
<HelloC />
<HelloC></HelloC>
</div>
)
}
export default App
约定说明
on + 事件名称 = { 事件处理程序 }
,比如:
在函数组件中:
// 函数组件
function HelloFn () {
// 定义事件回调函数
const clickHandler = () => {
console.log('事件被触发了')
}
return (
// 绑定事件
<button onClick={clickHandler}>click me!</button>
)
}
类组件中的事件绑定,整体的方式和函数组件差别不大,唯一需要注意的是:因为处于class类语境下,所以定义事件回调函数以及写法上有不同
import React from "react"
class CComponent extends React.Component {
// class Fields
clickHandler = (e, num) => {
// 这里的this指向的是正确的当前的组件实例对象
// 可以非常方便的通过this关键词拿到组件实例身上的其他属性或者方法
console.log(this)
}
clickHandler1 () {
// 这里的this 不指向当前的组件实例对象而指向undefined 存在this丢失问题
console.log(this)
}
render () {
return (
<div>
<button onClick={(e) => this.clickHandler(e, '123')}>click me</button>
<button onClick={this.clickHandler1}>click me</button>
</div>
)
}
}
function App () {
return (
<div>
<CComponent />
</div>
)
}
export default App
为什么这里会出现this指向不明的问题?
当然现在的主流写法都是函数组件,无需过多考虑this的指向问题。
事件对象是一个用来记录事件发生时的相关信息的对象,它可以用来解决一些事件处理的问题,比如:
事件对象的属性和方法可能因为不同的浏览器或事件类型而有所差异,所以在使用事件对象时,需要注意兼容性和适用性的问题。
获取事件对象e只需要在 事件的回调函数中 补充一个形参e即可拿到
// 函数组件
function HelloFn () {
// 定义事件回调函数
const clickHandler = (e) => {
console.log('事件被触发了', e)
}
return (
// 绑定事件
<button onClick={clickHandler}>click me!</button>
)
}
解决思路: 改造事件绑定为箭头函数,在箭头函数中完成参数的传递
import React from "react"
// 如何获取额外的参数?
// onClick={ onDel } -> onClick={ () => onDel(id) }
// 注意: 一定不要在模板中写出函数调用的代码 onClick = { onDel(id) } bad!!!!!!
const TestComponent = () => {
const list = [
{
id: 1001,
name: 'react'
},
{
id: 1002,
name: 'vue'
}
]
const onDel = (e, id) => {
console.log(e, id)
}
return (
<ul>
{list.map(item =>(
<li key={item.id}>
{item.name}
<button onClick={(e) => onDel(e, item.id)}>x</button>
</li>
))}
</ul>
)
}
function App () {
return (
<div>
<TestComponent />
</div>
)
}
export default App
很多人这个地方都是自己硬背,什么如果只传事件参数,那就直接写回调参函数名,如果还要传递额外参数,那就要用箭头函数包一下。其实完全没必要,只要理解了就都说通了,这个地方我们怎么去理解?
我们要知道:我们不需要主动去调用事件处理函数,我们只是把事件处理函数交出去,换句话说我们只需要写一个声明语法,而不是调用语法,当事件被触发的时候会有监听器或者计时器去调用。
一个前提:在React hook出来之前,函数式组件是没有自己的状态的,所以我们统一通过类组件来讲解
初始化状态
必须是state属性
)class Counter extends React.Component {
// 初始化状态
state = {
count: 0
}
render() {
return <button>计数器</button>
}
}
读取状态
class Counter extends React.Component {
// 初始化状态
state = {
count: 0
}
render() {
// 读取状态
return <button>计数器{this.state.count}</button>
}
}
修改状态
this.setState({ 要修改的部分数据 })
这里其实就是MVVM思想的体现,vue也是这个原理,其核心特点就是双向绑定,也就是说:当view变化的时候,数据也会改变。当数据发生变化的时候,view也会跟着改变。这里setState方法体现的就是后者。
class Counter extends React.Component {
// 定义数据
state = {
count: 0
}
// 定义修改数据的方法
setCount = () => {
this.setState({
count: this.state.count + 1
})
}
// 使用数据 并绑定事件
render () {
return <button onClick={this.setCount}>{this.state.count}</button>
}
}
概念:不要直接修改状态的值,而是基于当前状态创建新的状态值
state = {
count : 0,
list: [1,2,3],
person: {
name:'jack',
age:18
}
}
// 直接修改简单类型Number
this.state.count++
++this.state.count
this.state.count += 1
this.state.count = 1
// 直接修改数组
this.state.list.push(123)
this.state.list.spice(1,1)
// 直接修改对象
this.state.person.name = 'rose'
this.setState({
count: this.state.count + 1
list: [...this.state.list, 4],
person: {
...this.state.person,
// 覆盖原来的属性 就可以达到修改对象中属性的目的
name: 'rose'
}
})
使用React处理表单元素,一般有俩种方式:
什么是受控组件? input框自己的状态被React组件状态控制
React组件的状态的地方是在state中,input表单元素也有自己的状态是在value中,React将state与表单元素的值(value)绑定到一起,由state的值来控制表单元素的值,从而保证单一数据源特性
实现步骤:
以获取文本框的值为例,受控组件的使用步骤如下:
说白了就是将表单的值交给react的状态来控制。
代码实现:
import React from 'react'
class InputComponent extends React.Component {
// 声明组件状态
state = {
message: 'this is message',
}
// 声明事件回调函数
changeHandler = (e) => {
this.setState({ message: e.target.value })
}
render () {
return (
<div>
{/* 绑定value 绑定事件*/}
<input value={this.state.message} onChange={this.changeHandler} />
</div>
)
}
}
function App () {
return (
<div className="App">
<InputComponent />
</div>
)
}
export default App
什么是非受控组件?
非受控组件就是通过手动操作dom的方式获取文本框的值,文本框的状态不受react组件的state中的状态控制,直接通过原生dom获取输入框的值
实现步骤
代码实现:
import React, { createRef } from 'react'
class InputComponent extends React.Component {
// 使用createRef产生一个存放dom的对象容器
msgRef = createRef()
changeHandler = () => {
console.log(this.msgRef.current.value)
}
render() {
return (
<div>
{/* ref绑定 获取真实dom */}
<input ref={this.msgRef} />
<button onClick={this.changeHandler}>click</button>
</div>
)
}
}
function App () {
return (
<div className="App">
<InputComponent />
</div>
)
}
export default App
练习说明
注意:生成独立无二的id 可以使用 uuid 包 yarn add uuid
import { v4 as uuid } from 'uuid'
uuid() // 得到一个独一无二的id
app.js代码如下:
import './index.css'
import avatar from './images/avatar.png'
import React from 'react'
import { v4 as uuid } from 'uuid'
// 时间格式化
function formatDate (time) {
return `${time.getFullYear()}-${time.getMonth()}-${time.getDate()}`
}
class App extends React.Component {
state = {
// hot: 热度排序 time: 时间排序
tabs: [
{
id: 1,
name: '热度',
type: 'hot'
},
{
id: 2,
name: '时间',
type: 'time'
}
],
active: 'hot',
list: [
{
id: 1,
author: '刘德华',
comment: '给我一杯忘情水',
time: new Date('2021-10-10 09:09:00'),
// 1: 点赞 0:无态度 -1:踩
attitude: 1
},
{
id: 2,
author: '周杰伦',
comment: '哎哟,不错哦',
time: new Date('2021-10-11 09:09:00'),
// 1: 点赞 0:无态度 -1:踩
attitude: 0
},
{
id: 3,
author: '五月天',
comment: '不打扰,是我的温柔',
time: new Date('2021-10-11 10:09:00'),
// 1: 点赞 0:无态度 -1:踩
attitude: -1
}
],
userComment:""
}
//tab栏切换回调事件
tabSwitch = (type) => {
if (type === 'hot') {
this.setState({
active: 'hot',
list:this.state.list.sort(
(item1,item2) => -(item1.attitude - item2.attitude)
)
}
)
}else {
this.setState(
{
active: 'time',
list:this.state.list.sort(
(item1,item2) => -(item1.time - item2.time)
)
}
)
}
}
//添加评论回调函数
addComment = () => {
//写法1:
//push方法的返回值是数组长度,不是数组本身
// const newList = [...this.state.list]
// newList.push(
// {
// id: uuid(),
// author: '临时用户',
// comment: this.state.userComment,
// time: new Date(),
// // 1: 点赞 0:无态度 -1:踩
// attitude: 0
// }
// )
// this.setState(
// {
// list:newList,
// userComment:""
// }
// )
//写法2:解构赋值
this.setState(
{
list:[...this.state.list,
{
id: uuid(),
author: '临时用户',
comment: this.state.userComment,
time: new Date(),
// 1: 点赞 0:无态度 -1:踩
attitude: 0
}
],
userComment:""
}
)
}
//评论框变化回调函数
commentChange = (e) => {
console.log(e.target.value)
this.setState(
{
userComment: e.target.value
}
)
}
//删除评论的回调事件
deleteComment = (id) => {
this.setState(
{
list:this.state.list.filter(
item => item.id !== id
)
}
)
}
render () {
return (
<div className="App">
<div className="comment-container">
{/* 评论数 */}
<div className="comment-head">
<span>5 评论</span>
</div>
{/* 排序 */}
<div className="tabs-order">
<ul className="sort-container">
{
this.state.tabs.map(tab => (
<li
key={tab.id}
className={tab.type === this.state.active ? 'on' : ''}
onClick={() => this.tabSwitch(tab.type)}
>按{tab.name}排序</li>
))
}
</ul>
</div>
{/* 添加评论 */}
<div className="comment-send">
<div className="user-face">
<img className="user-head" src={avatar} alt="" />
</div>
<div className="textarea-container">
<textarea
cols="80"
rows="5"
placeholder="发条友善的评论"
className="ipt-txt"
value={this.state.userComment}
onChange={this.commentChange}
/>
<button className="comment-submit" onClick={this.addComment}>发表评论</button>
</div>
<div className="comment-emoji">
<i className="face"></i>
<span className="text">表情</span>
</div>
</div>
{/* 评论列表 */}
<div className="comment-list">
{
this.state.list.map(item => (
<div className="list-item" key={item.id}>
<div className="user-face">
<img className="user-head" src={avatar} alt="" />
</div>
<div className="comment">
<div className="user">{item.author}</div>
<p className="text">{item.comment}</p>
<div className="info">
<span className="time">{formatDate(item.time)}</span>
<span className={item.attitude === 1 ? 'like liked' : 'like'}>
<i className="icon" />
</span>
<span className={item.attitude === -1 ? 'hate hated' : 'hate'}>
<i className="icon" />
</span>
<span className="reply btn-hover" onClick={() => this.deleteComment(item.id)}>删除</span>
</div>
</div>
</div>
))
}
</div>
</div>
</div>)
}
}
export default App