核心概念
1. 创建和嵌套组件
React 应用程序是由 组件 组成的。一个组件是 UI(用户界面)的一部分,它拥有自己的逻辑和外观。组件可以小到一个按钮,也可以大到整个页面
React 组件必须以大写字母开头,而 HTML 标签则必须是小写字母。
function MyButton() {
return (
<button>I'm a button</button>
);
}
export default function MyApp() {
return (
<div>
<h1>Welcome to my app</h1>
<MyButton />
</div>
);
}
2. 使用jsx
JSX 比 HTML 更加严格。你必须闭合标签,如
<br />
。你的组件也不能返回多个 JSX 标签。你必须将它们包裹到一个共享的父级中,比如<div>...</div>
或使用空的<>...</>
包裹
function AboutPage() {
return (
<>
<h1>About</h1>
<p>Hello there.<br />How do you do?</p>
</>
);
}
3. 添加样式
在 React 中,你可以使用
className
来指定一个 CSS 的 class。它与 HTML 的
<a href="" title="" target="_blank" class="link">class</a>
属性的工作方式相同
<img className="user" />
.user{
background:red;
}
内联样式
<div style={{width:"300px",backgroundColor:"red"}}></div>
4. 显示数据
标签中的 { } 内,可以执行 js 代码
const [visiable,setVisiable] = useState<Boolean>(false);
const title = "标题";
return (
<>
{
visiable ? <h1>{ title }</h1> : null
}
</>
)
将变量user.imageUrl 赋值给 img 标签的 src 属性,需要使用{} 而非 “”。
<img src="user" /> // 将“user”字符串传递给src
<img src={userPng} /> // 将变量userPng传递给src
5. 条件渲染
React 没有特殊的语法来编写条件语句,因此你使用的就是普通的 JavaScript 代码。例如使用 if 语句根据条件引入 JSX
let content;
if (isLoggedIn) {
content = <AdminPanel />;
} else {
content = <LoginForm />;
}
return (
<div>
{content}
</div>
);
// 条件 ?运算符
<div>
{isLoggedIn ? (
<AdminPanel />
) : (
<LoginForm />
)}
</div>
// 逻辑 && 语法
<div>
{isLoggedIn && <AdminPanel />}
</div>
6. 渲染列表
注意, 必须设置
key
属性。类同vue中的 v-for中的 key
const products = [{id:1,title:"name1"},{id:2,title:"name2"}]
return (
<ul>{
products.map(product =>
<li key={product.id}>
{product.title}
</li>
)}
</ul>
);
7. 响应事件
注意,
onClick={handleClick}
的结尾没有小括号!不要 调用 事件处理函数:你只需 把函数传递给事件 即可。当用户点击按钮时 React 会调用你传递的事件处理函数
function MyButton() {
function handleClick() {
alert('You clicked me!');
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
8. 更新界面
useState(),类似与vue3中ref(),reactive()等,创建响应式数据,数据改变时会更新到界面
当React重新渲染一个组件时:
- React 会再次调用你的函数
- 函数会返回新的 JSX 快照
- React 会更新界面以匹配返回的快照
import { useState } from 'react';
export default function MyApp() {
return (
<div>
<h1>Counters that update separately</h1>
<MyButton />
<MyButton />
</div>
);
}
function MyButton() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<button onClick={handleClick}>
Clicked {count} times
</button>
);
}
9. 使用Hook
以use开头的函数被称为Hook。useState 是React提供的一个内置Hook。可以通过组合现有Hook来编写自己的Hook。
Hook 比普通函数更为严格。你只能在你的组件(或其他 Hook)的 顶层 调用 Hook。如果你想在一个条件或循环中使用
useState
,请提取一个新的组件并在组件内部使用它。
10. 组件间共享数据
变量上移至父组件,通过prop传递
export default function MyApp() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<div>
<h1>Counters that update together</h1>
<MyButton count={count} onClick={handleClick} />
<MyButton count={count} onClick={handleClick} />
</div>
);
}
function MyButton({ count, onClick }) {
return (
<button onClick={onClick}>
Clicked {count} times
</button>
);
}
11. 构建state 准则
当你编写一个存有 state 的组件时,你需要选择使用多少个 state 变量以及它们都是怎样的数据格式。尽管选择次优的 state 结构下也可以编写正确的程序,但有几个原则可以指导您做出更好的决策:
- 合并关联的 state。如果你总是同时更新两个或更多的 state 变量,请考虑将它们合并为一个单独的 state 变量。
- 避免互相矛盾的 state。当 state 结构中存在多个相互矛盾或“不一致”的 state 时,你就可能为此会留下隐患。应尽量避免这种情况。
- 避免冗余的 state。如果你能在渲染期间从组件的 props 或其现有的 state 变量中计算出一些信息,则不应将这些信息放入该组件的 state 中。
- 避免重复的 state。当同一数据在多个 state 变量之间或在多个嵌套对象中重复时,这会很难保持它们同步。应尽可能减少重复。
- 避免深度嵌套的 state。深度分层的 state 更新起来不是很方便。如果可能的话,最好以扁平化方式构建 state。
安装
1. 启动一个新的React项目
基础脚手架
- npx create-react-app
全栈架构
- Next.js
npx create-next-app@latest
- Remix
npx create-remix
Next.js 的 App Router 是对 Next.js API 的重新设计,旨在实现 React 团队的全栈架构愿景。它让你在异步组件中获取数据,这些组件甚至能在服务端构建过程中运行。
React官方与Next.js团队合作,研究、开发、集成和测试与框架无关的 React 前沿功能
2. 使用TypeScript
TypeScript 天然支持 JSX——只需在项目中添加
@types/react
和@types/react-dom
即可获得完整的 React Web 支持
npm install @types/react @types/react-dom
内置Hook
Hook 可以帮助在组件中使用不同的 React 功能。你可以使用内置的 Hook 或使用自定义 Hook
State Hook
Context Hook
上下文帮助组件 从祖先组件接收信息,而无需将其作为 props 传递
使用useContext读取订阅上下文
function Button(){
const theme = useContext(ThemeContext);
// ...
}
Ref Hook
ref 允许组件 保存一些不用于渲染的信息,比如 DOM 节点或 timeout ID。与状态不同,更新 ref 不会重新渲染组件。ref 是从 React 范例中的“脱围机制”。当需要与非 React 系统如浏览器内置 API 一同工作时,ref 将会非常有用。
- 使用
useRef
声明 ref。你可以在其中保存任何值,但最常用于保存 DOM 节点。 - 使用
useImperativeHandle
自定义从组件中暴露的 ref,但是很少使用。
const iptRef = useRef(null);
const timer = useRef(null);
useEffect(()=>{
iptRef.current.focus();
timer.current = setInterval(()=>{
todo();
},100);
return ()=>{
clearInterval(timer.current);
}
},[])
return (<>
<input ref={inputRef}/>
</>)
Effect Hook
处理网络、浏览器、DOM、动画、使用不同 UI 库编写的小部件以及其他非 React 代码。
- 使用
useEffect
将组件连接到外部系统。
function CardList(){
const [list,setList] = useState<Array<{id:string;title:string}>>([]);
const [page,setPage] = useState(0);
useEffect((
// 第二个参数不存在 或者为空时 组件初始化完成 执行该函数
axios.get().then((res)=>{
todo...
})
return ()=>{
// 组件卸载时 执行该函数
}
),[])
useEffect(()=>{
// page变化时 执行该函数
},[page])
return <ol>
{
list.map((item)=>{
return (<li key={item.id}>{item.title}</li>)
})
}
</ol>
}
useLayoutEffect
在浏览器重新绘制屏幕前执行,可以在此处测量布局。 (较少使用)useInsertionEffect
在 React 对 DOM 进行更改之前触发,库可以在此处插入动态 CSS。(较少使用)
性能Hook
可以使用以下 Hook 跳过计算和不必要的重新渲染:
- 使用
useMemo
缓存计算代价昂贵的计算结果。 - 使用
useCallback
将函数传递给优化组件之前缓存函数定义。
当屏幕确实需要更新,无法跳过重新渲染。在这种情况下,可以通过将必须同步的阻塞更新(比如使用输入法输入内容)与不需要阻塞用户界面的非阻塞更新(比如更新图表)分离以提高性能。
useTransition
允许将状态转换标记为非阻塞,并允许其他更新中断它。useDeferredValue
允许延迟更新 UI 的非关键部分,以让其他部分先更新。
资源Hook
use(resource)
是一个 React Hook,它可以让你读取类似于 Promise 或 context 的资源的值。
与其他Hook不同,它可以在循环和条件语句中调用. 但是调用use的函数必须是一个组件或Hook
注意: use(Promise) ,当promise处于pendding时,整个组件或者Hook都会挂起,直到resolve后,重新执行组件或者hook. 而async/await 则不同,仅在await处pending, resolve后从await处继续执行.
- context
import { createContext, use } from 'react';
const ThemeContext = createContext(null);
export default function MyApp() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
)
}
function Form() {
return (
<Panel title="Welcome">
<Button show={true}>Sign up</Button>
<Button show={false}>Log in</Button>
</Panel>
);
}
function Panel({ title, children }) {
const theme = use(ThemeContext);
const className = 'panel-' + theme;
return (
<section className={className}>
<h1>{title}</h1>
{children}
</section>
)
}
function Button({ show, children }) {
if (show) {
const theme = use(ThemeContext);
const className = 'button-' + theme;
return (
<button className={className}>
{children}
</button>
);
}
return false
}
- promise
reject时需要使用错误边界处理
import React, { ErrorBoundary, Suspense, use } from "react";
export function User ({ getUser}) {
return (
<ErrorBoundary fallback={<p>catch error...</p>}>
<Suspense fallback={<p>loading...</p>}>
<Message getUser={getUser} />
</Suspense>
</ErrorBoundary>
);
}
function getUser = axios.get("");
function Message({ getUser}) {
const content = use(getUser);
return <p>user: {content}</p>;
}
useReducer
常用内置组件
Fragment
<Fragment>
通常使用<>...</>
替代,它们都允许你在不添加额外节点的情况下将子元素组合.
注意:
Suspense
<Suspense fallback={<Loading />}>
{/* 当SomeComponent未加载完成时,渲染Loading组件 */}
<SomeComponent />
</Suspense>
children
:真正的 UI 渲染内容。如果children
在渲染中被挂起,Suspense 边界将会渲染fallback
。fallback
:真正的 UI 未渲染完成时代替其渲染的备用 UI,它可以是任何有效的 React 节点。后备方案通常是一个轻量的占位符,例如表示加载中的图标或者骨架屏。当children
被挂起时,Suspense 将自动切换至渲染fallback
;当数据准备好时,又会自动切换至渲染children
。如果fallback
在渲染中被挂起,那么将自动激活最近的 Suspense 边界。