-
组件中使用了 jsx 语法,即在 js 中直接使用 html 标签或组件标签
-
函数式组件必须返回标签片段
在 index.js 引入组件
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import reportWebVitals from './reportWebVitals'
// 1. 引入组件
import Hello from './pages/Hello'
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(
<React.StrictMode>
{/* 2. 将原来的 <App/> 改为 <Hello></Hello> */}
<Hello></Hello>
</React.StrictMode>
)
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals()
将欢迎词作为属性传递给组件
<Hello msg='你好'></Hello>
-
字符串值,可以直接使用双引号赋值
-
其它类型的值,用
{值}
而组件修改为
export default function Hello(props: { msg: string }) {
return <h3>{props.msg}</h3>
}
jsx 原理
export default function Hello(props: { msg: string }) {
return <h3>{props.msg}</h3>
}
在 v17 之前,其实相当于
import { createElement } from "react";
export default function Hello(props: {msg: string}) {
return createElement('h3', null, `${props.msg}`)
}
3) 人物卡片案例
样式已经准备好 /src/css/P1.css
#root {
display: flex;
width: 100vw;
height: 100vh;
justify-content: center;
align-items: center;
}
div.student {
flex-shrink: 0;
flex-grow: 0;
position: relative;
width: 128px;
height: 330px;
/* font-family: '华文行楷'; */
font-size: 14px;
text-align: center;
margin: 20px;
display: flex;
justify-content: flex-start;
background-color: #7591AD;
align-items: center;
flex-direction: column;
border-radius: 5px;
box-shadow: 0 0 8px #2c2c2c;
color: #e8f6fd;
}
.photo {
position: absolute;
width: 100%;
height: 100%;
top: 0;
border-radius: 0%;
overflow: hidden;
transition: 0.3s;
border-radius: 5px;
}
.photo img {
width: 100%;
height: 100%;
/* object-fit: scale-down; */
object-fit: cover;
}
.photo::before {
position: absolute;
content: '';
width: 100%;
height: 100%;
background-image: linear-gradient(to top, #333, transparent);
}
div.student h2 {
position: absolute;
font-size: 20px;
width: 100%;
height: 68px;
font-weight: normal;
text-align: center;
margin: 0;
line-height: 68px;
visibility: hidden;
}
h2::before {
position: absolute;
top: 0;
left: 0;
content: '';
width: 100%;
height: 68px;
background-color: rgba(0, 0, 0, 0.3);
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
div.student h1 {
position: absolute;
top: 250px;
font-size: 22px;
margin: 0;
transition: 0.3s;
font-weight: normal;
}
div.student p {
margin-top: 300px;
width: 80%;
font-weight: normal;
text-align: center;
padding-bottom: 5px;
border-bottom: 1px solid #8ea2b8;
}
.student:hover .photo::before {
display: none;
}
.student:hover .photo {
width: 90px;
height: 90px;
top: 90px;
border-radius: 50%;
box-shadow: 0 0 15px #111;
}
.student:hover img {
object-position: 50% 0%;
}
.student:hover h1 {
position: absolute;
top: 190px;
width: 40px;
}
div.student:hover h2 {
visibility: visible;
}
类型 /src/model/Student.ts
export interface Student {
id: number,
name: string,
sex: string,
age: number,
photo: string
}
组件 /src/pages/P1.tsx
import { Student } from '../model/Student'
import '../css/P1.css'
export default function P1(props: { student: Student }) {
return (
<div className='student'>
<div className='photo'>
<img src={props.student.photo}/>
</div>
<h1>{props.student.name}</h1>
<h2>{props.student.id}</h2>
<p>性别 {props.student.sex} 年龄 {props.student.age}</p>
</div>
)
}
使用组件
const stu1 = { id: 1, name: '张三', sex: '男', age: 99, photo: '/imgs/1.png' }
const stu2 = { id: 2, name: '李四', sex: '男', age: 20, photo: '/imgs/2.png' }
const stu3 = { id: 3, name: '王五', sex: '男', age: 30, photo: '/imgs/3.png'}
<P1 student={stu1}></P1>
<P1 student={stu2}></P1>
<P1 student={stu3}></P1>
路径
-
src 下的资源,要用相对路径引入
-
public 下的资源,记得 / 代表路径的起点
标签命名
-
组件标签必须用大驼峰命名
-
普通 html 标签必须用小写命名
事件处理
import { Student } from '../model/Student'
import '../css/P1.css'
export default function P1(props: { student: Student }) {
function handleClick(e : React.MouseEvent){
console.log(student)
console.log(e)
}
return (
<div className='student'>
<div className='photo' onClick={handleClick}>
<img src={props.student.photo}/>
</div>
<h1>{props.student.name}</h1>
<h2>{props.student.id}</h2>
<p>性别 {props.student.sex} 年龄 {props.student.age}</p>
</div>
)
}
-
事件以小驼峰命名
-
事件处理函数可以有一个事件对象参数,可以获取事件相关信息
-
列表 & Key
import { Student } from '../model/Student' import P1 from './P1' export default function P2(props: { students: Student[] }) { return ( <> {props.students.map((s) => ( <P1 student={s} key={s.id}></P1> ))} </> ) }
-
key 在循环时是必须的,否则会有 warning
-
也可以这么做
-
import { Student } from '../model/Student' import P1 from './P1' export default function P2(props: { students: Student[] }) { const list = props.students.map((s) => <P1 student={s} key={s.id}></P1>) return <>{list}</> }
使用组件
-
const stu1 = { id: 1, name: '张三', sex: '男', age: 99, photo: '/imgs/1.png' } const stu2 = { id: 2, name: '李四', sex: '男', age: 20, photo: '/imgs/2.png' } const stu3 = { id: 3, name: '王五', sex: '男', age: 30, photo: '/imgs/3.png'} <P2 students={[stu1,stu2,stu3]}></P2>
条件渲染
P1 修改为
import { Student } from '../model/Student' import '../css/P1.css' export default function P1(props: { student: Student; hideAge?: boolean }) { function handleClick() { console.log(props.student) } const ageFragment = !props.hideAge && <span>年龄 {props.student.age}</span> return ( <div className='student'> <div className='photo' onClick={handleClick}> <img src={props.student.photo} /> </div> <h1>{props.student.name}</h1> <h2>{props.student.id}</h2> <p> 性别 {props.student.sex} {ageFragment} </p> </div> ) }
-
子元素如果是布尔值,nullish,不会渲染
-
P2 修改为
-
import { Student } from '../model/Student' import P1 from './P1' export default function P2(props: { students: Student[]; hideAge?: boolean }) { const list = props.students.map((s) => ( <P1 student={s} hideAge={props.hideAge} key={s.id}></P1> )) return <>{list}</>
使用组件
-
const stu1 = { id: 1, name: '张三', sex: '男', age: 99, photo: '/1.png' } const stu2 = { id: 2, name: '李四', sex: '男', age: 45, photo: '/2.png' } const stu3 = { id: 3, name: '王五', sex: '男', age: 45, photo: '/3.png'} <P2 students={[stu1,stu2,stu3]} hideAge={true}></P2>
参数解构
以 P1 组件为例
import { Student } from '../model/Student' import '../css/P1.css' export default function P1 ({ student, hideAge = false }: { student: Student, hideAge?: boolean }) { function handleClick() { console.log(student) } const ageFragment = !hideAge && <span>年龄 {student.age}</span> return ( <div className='student'> <div className='photo' onClick={handleClick}> <img src={student.photo} /> </div> <h1>{student.name}</h1> <h2>{student.id}</h2> <p> 性别 {student.sex} {ageFragment} </p> </div> ) }
-
可以利用解构赋值语句,让 props 的使用更为简单
-
对象解构赋值还有一个额外的好处,给属性赋默认值
const stu1 = { id: 1, name: '张三', sex: '男', age: 99, photo: '/1.png' } <P1 student={stu1}></P1>
4) 处理变化的数据
入门案例侧重的是数据展示,并未涉及到数据的变动,接下来我们开始学习 react 如何处理数据变化
axios
首先来学习 axios,作用是发送请求、接收响应,从服务器获取真实数据
安装
npm install axios
定义组件
-
import axios from 'axios' export default function P4({ id }: { id: number }) { async function updateStudent() { const resp = await axios.get(`http://localhost:8080/api/students/${id}`) console.log(resp.data.data) } updateStudent() return <></> }
-
其中 /api/students/${id} 是提前准备好的后端服务 api,会延迟 2s 返回结果
-
使用组件
-
<P4 id={1}></P4>
在控制台上打印
-
{ "id": 1, "name": "宋远桥", "sex": "男", "age": 40 }
当属性变化时,会重新触发 P4 组件执行,例如将 id 从 1 修改为 2
执行流程
-
首次调用函数组件,返回的 jsx 代码会被渲染成【虚拟 dom 节点】(也称 Fiber 节点)
-
根据【虚拟 dom 节点】会生成【真实 dom 节点】,由浏览器显示出来
-
-
当函数组件的 props 或 state 发生变化时,才会重新调用函数组件,返回 jsx
-
jsx 与上次的【虚拟 dom 节点】对比
-
如果没变化,复用上次的节点
-
有变化,创建新的【虚拟 dom 节点】替换掉上次的节点
-
-
-
由于严格模式会触发两次渲染,为了避免干扰,请先注释掉 index.tsx 中的
<React.StrictMode>