-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
React进阶开发 设计系统, 设计模式, 性能优化Advanced React Design System, Design Patterns, Performance/[进行中] #193
Comments
2. 设计模式布局组件 Design Patterns Layout Components2. 介绍3. 屏幕分割器 Screen Splitternpm create vite@latest react-dp
react
typescript
pnpm i
pnpm i styled-components -S
pnpm run dev
import React from "react";
import { styled } from "styled-components";
const Container = styled.div`
display: flex;
`;
const Panel = styled.div`
flex: 1;
`;
interface SplitScreenProps {
Left: React.ComponentType;
Right: React.ComponentType;
}
//使用 SplitScreenProps 作为 props 类型,并显式声明返回类型为 React.ReactElement。
export const SplitScreen = ({
Left,
Right,
}: SplitScreenProps): React.ReactElement => {
return (
<Container>
<Panel>
<Left />
</Panel>
<Panel>
<Right />
</Panel>
</Container>
);
};
import "./App.css";
import { SplitScreen } from "./components/split-screen";
const LeftSideComp = () => {
return <h2 style={{ backgroundColor: "red" }}>left</h2>;
};
const RightSideComp = () => {
return <h2 style={{ backgroundColor: "blue" }}>right</h2>;
};
function App() {
return <SplitScreen Left={LeftSideComp} Right={RightSideComp} />;
}
export default App; 4. 屏幕分割器增强 Screen Splitter Enhancement
import React from 'react';
import { styled } from 'styled-components';
// 定义 styled-components 的类型
const Container = styled.div`
display: flex;
`;
interface PanelProps {
flex: number;
}
const Panel = styled.div<PanelProps>`
flex: ${(p) => p.flex};
`;
// 定义 SplitScreen 组件的 props 类型
interface SplitScreenProps {
children: [React.ReactNode, React.ReactNode];
leftWidth?: number;
rightWidth?: number;
}
export const SplitScreen = ({
children,
leftWidth = 1,
rightWidth = 1,
}: SplitScreenProps): React.ReactElement => {
const [left, right] = children;
return (
<Container>
<Panel flex={leftWidth}>{left}</Panel>
<Panel flex={rightWidth}>{right}</Panel>
</Container>
);
};
import React from 'react';
import './App.css';
import { SplitScreen } from './components/split-screen';
interface SideCompProps {
title: string;
}
const LeftSideComp = ({ title }: SideCompProps): React.ReactElement => {
return <h2 style={{ backgroundColor: 'crimson' }}>{title}</h2>;
};
const RightSideComp = ({ title }: SideCompProps): React.ReactElement => {
return <h2 style={{ backgroundColor: 'burlywood' }}>{title}</h2>;
};
function App(): React.ReactElement {
return (
<SplitScreen leftWidth={1} rightWidth={3}>
<LeftSideComp title="Left" />
<RightSideComp title="Right" />
</SplitScreen>
);
}
export default App; 5. 列表 Lists
import React from 'react';
export interface Author {
name: string;
age: number;
country: string;
books: string[];
}
interface LargeAuthorListItemProps {
author: Author;
}
export const LargeAuthorListItem = ({ author }: LargeAuthorListItemProps): React.ReactElement => {
const { name, age, country, books } = author;
return (
<>
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Country: {country}</p>
<h2>Books</h2>
<ul>
{books.map((book) => (
<li key={book}>{book}</li>
))}
</ul>
</>
);
};
// components/authors/SmallAuthorListItem.tsx
import React from 'react';
import { Author } from './LargeListItems';
interface SmallAuthorListItemProps {
author: Pick<Author, 'name' | 'age'>;
}
export const SmallAuthorListItem = ({ author }: SmallAuthorListItemProps): React.ReactElement => {
const { name, age } = author;
return (
<p>Name: {name}, Age: {age}</p>
);
};
import React from 'react';
interface RegularListProps<T> {
items: T[];
sourceName: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ItemComponent: any ;
}
export const RegularList = <T,>({ items, sourceName, ItemComponent }: RegularListProps<T>): React.ReactElement => {
return (
<>
{items.map((item, i) => (
<ItemComponent key={i} {...{ [sourceName]: item }} />
))}
</>
);
};
export const authors = [
{
name: "Sarah Waters",
age: 55,
country: "United Kingdom",
books: ["Fingersmith", "The Night Watch"],
},
{
name: "Haruki Murakami",
age: 71,
country: "Japan",
books: ["Norwegian Wood", "Kafka on the Shore"],
},
{
name: "Chimamanda Ngozi Adichie",
age: 43,
country: "Nigeria",
books: ["Half of a Yellow Sun", "Americanah"],
},
];
import { LargeAuthorListItem } from "./components/authors/LargeListItems";
import { SmallAuthorListItem } from "./components/authors/SmallListItems";
import { RegularList } from "./components/lists/Regular";
import { authors } from "./data/authors";
function App() {
return (
<>
<RegularList
items={authors}
sourceName={"author"}
ItemComponent={SmallAuthorListItem}
/>
<RegularList
items={authors}
sourceName={"author"}
ItemComponent={LargeAuthorListItem}
/>
</>
);
}
export default App; 6. 列表类型 Lists Types
import React from 'react';
export interface Book {
name: string;
price: number;
title: string;
pages: number;
}
interface LargeBookListItemProps {
book: Book;
}
export const LargeBookListItem = ({ book }: LargeBookListItemProps): React.ReactElement => {
const { name, price, title, pages } = book;
return (
<>
<h2>{name}</h2>
<p>{price}</p>
<h2>Title:</h2>
<p>{title}</p>
<p># of Pages: {pages}</p>
</>
);
};
import React from 'react';
import { Book } from './LargeListItems';
interface SmallBookListItemProps {
book: Pick<Book, 'name' | 'price'>;
}
export const SmallBookListItem = ({ book }: SmallBookListItemProps): React.ReactElement => {
const { name, price } = book;
return (
<h2>{name} / {price}</h2>
);
};
import React from "react";
interface NumberedListProps<T> {
items: T[];
sourceName: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ItemComponent: any; //React.ComponentType<{ [key: string]: T }>;
}
export const NumberedList = <T,>({
items,
sourceName,
ItemComponent,
}: NumberedListProps<T>): React.ReactElement => {
return (
<>
{items.map((item, i) => {
const props = { [sourceName]: item };
return (
<React.Fragment key={i}>
<h3>{i + 1}</h3>
<ItemComponent {...props} />
</React.Fragment>
);
})}
</>
);
};
import { LargeAuthorListItem } from "./components/authors/LargeListItems";
import { SmallAuthorListItem } from "./components/authors/SmallListItems";
import { LargeBookListItem } from "./components/books/LargeListItems";
import { SmallBookListItem } from "./components/books/SmallListItems";
import { NumberedList } from "./components/lists/Numbered";
import { RegularList } from "./components/lists/Regular";
import { authors } from "./data/authors";
import { books } from "./data/books";
function App() {
return (
<>
<RegularList
items={authors}
sourceName={"author"}
ItemComponent={SmallAuthorListItem}
/>
<NumberedList
items={authors}
sourceName={"author"}
ItemComponent={LargeAuthorListItem}
/>
<RegularList
items={books}
sourceName={"book"}
ItemComponent={SmallBookListItem}
/>
<NumberedList
items={books}
sourceName={"book"}
ItemComponent={LargeBookListItem}
/>
</>
);
}
export default App; 7. 模态框 Modals非受控,因为父级无法从模态框外部控制模态框的状态。 这个模型是非受控的,因为这个模型本身可以控制自己,比如显示和隐藏组件的 show 和 setShow。 我们说它是非受控的,因为外部组件无法直接访问它的特性。 因为我无法访问这个模型的状态,包括 show 和 setShow,这降低了模型的灵活性,因为它是非受控的。
import React, { useState, ReactNode } from 'react';
import { styled } from 'styled-components';
const ModalBackground = styled.div`
position: absolute;
left: 0;
top: 0;
overflow: auto;
background-color: #00000067;
width: 100%;
height: 100%;
`;
const ModalContent = styled.div`
margin: 12% auto;
padding: 24px;
background-color: wheat;
width: 50%;
`;
interface ModalProps {
children: ReactNode;
}
export const Modal = ({ children }: ModalProps): React.ReactElement => {
const [show, setShow] = useState<boolean>(false);
return (
<>
<button onClick={() => setShow(true)}>Show Modal</button>
{show && (
<ModalBackground onClick={() => setShow(false)}>
<ModalContent onClick={(e) => e.stopPropagation()}>
<button onClick={() => setShow(false)}>Hide Modal</button>
{children}
</ModalContent>
</ModalBackground>
)}
</>
);
};
import { Modal } from "./components/Modal";
import { LargeBookListItem } from "./components/books/LargeListItems";
import { books } from "./data/books";
function App() {
return (
<>
<Modal>
<LargeBookListItem book={books[0]} />
</Modal>
</>
);
}
export default App; |
3. 设计模式-容器组件 Design Patterns Container Components1. 介绍 Introduction
通常,如果你是一个初级或中级的React开发者,可能会让子组件自行加载数据并独立显示。 例如,你可能会使用Usestate和Useeffect钩子以及像Axios或Fetch这样的库来从服务器获取数据。 然而,当多个子组件需要共享相同的数据加载逻辑时,就会出现问题。 这时,容器组件就派上用场了。 它们通过将数据加载逻辑提取到一个专门的组件中来解决这个问题。 容器组件负责数据检索过程,并将数据自动传递给子组件。 很快我们将深入探讨容器组件如何实现这一点。 但在此之前,让我们先了解容器组件背后的核心概念,类似于布局组件,我们旨在让子组件不必了解它们所处的特定布局。 容器组件遵循类似的原则。 我们希望组件不知道其数据的来源或管理方式。 相反,它们只需接收props并显示相关内容,而无需了解底层的数据处理。 2. 服务器设置 Server Setuppnpm i express -D
//const express = require("express");
import express from 'express';
const app = express();
app.use(express.json());
let currentUser = {
name: "Sarah Waters",
age: 55,
country: "United Kingdom",
books: ["Fingersmith", "The Night Watch"],
};
let users = [
{
name: "Sarah Waters",
age: 55,
country: "United Kingdom",
books: ["Fingersmith", "The Night Watch"],
},
{
name: "Haruki Murakami",
age: 71,
country: "Japan",
books: ["Norwegian Wood", "Kafka on the Shore"],
},
{
name: "Chimamanda Ngozi Adichie",
age: 43,
country: "Nigeria",
books: ["Half of a Yellow Sun", "Americanah"],
},
];
let books = [
{
name: "To Kill a Mockingbird",
pages: 281,
title: "Harper Lee",
price: 12.99,
},
{
name: "The Catcher in the Rye",
pages: 224,
title: "J.D. Salinger",
price: 9.99,
},
{
name: "The Little Prince",
pages: 85,
title: "Antoine de Saint-Exupéry",
price: 7.99,
},
];
app.get("/current-user", (req, res) => res.json(currentUser));
app.get("/users/:id", (req, res) => {
const { id } = req.params;
console.log(id);
res.json(users.find((user) => user.id === id));
});
app.get("/users", (req, res) => res.json(users));
app.post("/users/:id", (req, res) => {
const { id } = req.params;
const { user: editedUser } = req.body;
users = users.map((user) => (user.id === id ? editedUser : user));
res.json(users.find((user) => user.id === id));
});
app.get("/books", (req, res) => res.json(books));
app.get("/books/:id", (req, res) => {
const { id } = req.params;
res.json(books.find((book) => book.id === id));
});
let SERVER_PORT = 9090;
app.listen(SERVER_PORT, () =>
console.log(`Server is listening on port: ${SERVER_PORT}`)
); 配置 react 到服务器的代理普通React 项目
{
"name": "react-design-patterns",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:9090",
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"styled-components": "^6.0.0-rc.3",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/node_modules/**": true
},
"devDependencies": {
"express": "^4.19.2"
}
}
const response = await axios.get('/current-user'); Vite React 项目 [本项目使用该方式]
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/api': {
target: 'http://localhost:9090',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
});
const response = await axios.get('/api/current-user'); 运行服务器node server.js 3. 当前用户数据加载组件 3. Loader Component for CurrentUser Datapnpm i axios -S 子组件 user-info
import React from 'react';
// 定义 User 类型
type User = {
name: string;
age: number;
country: string;
books: string[];
};
// 定义组件的 Props 类型
type UserInfoProps = {
user?: User;
};
// 使用 FC 和 Props 类型定义组件
export const UserInfo= ({ user }:UserInfoProps) : React.ReactElement=> {
const { name, age, country, books } = user || {} as User;
return user ? (
<>
<h2>{name}</h2>
<p>Age: {age} years</p>
<p>Country: {country}</p>
<h2>Books</h2>
<ul>
{books.map((book) => (
<li key={book}>{book}</li>
))}
</ul>
</>
) : (
<h1>Loading...</h1>
);
}; 容器组件 current-user-loader
import axios from "axios";
import React, { useEffect, useState, ReactElement } from "react";
// 定义 User 类型
type User = {
name: string;
age: number;
country: string;
books: string[];
};
// 定义组件的 Props 类型
type CurrentUserLoaderProps = {
children: ReactElement<{ user: User | null }>;
};
// 使用 FC 和 Props 类型定义组件
export const CurrentUserLoader = ({
children,
}: CurrentUserLoaderProps): React.ReactElement => {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
(async () => {
const response = await axios.get("/api/current-user");
setUser(response.data);
})();
}, []);
return <>{React.cloneElement(children, { user })}</>;
}; App
import { CurrentUserLoader } from "./components/current-user-loader";
import { UserInfo } from "./components/user-info";
function App() {
return (
<>
<CurrentUserLoader>
<UserInfo />
</CurrentUserLoader>
</>
);
}
export default App; 4. 用户数据加载组件 Loader Component for User Data之前的组件,只能获取当前用户的数据。 服务器
//const express = require("express");
import express from 'express';
const app = express();
app.use(express.json());
let currentUser = {
id: "1",
name: "Sarah Waters",
age: 55,
country: "United Kingdom",
books: ["Fingersmith", "The Night Watch"],
};
let users = [
{
id: "1",
name: "Sarah Waters",
age: 55,
country: "United Kingdom",
books: ["Fingersmith", "The Night Watch"],
},
{
id: "2",
name: "Haruki Murakami",
age: 71,
country: "Japan",
books: ["Norwegian Wood", "Kafka on the Shore"],
},
{
id: "3",
name: "Chimamanda Ngozi Adichie",
age: 43,
country: "Nigeria",
books: ["Half of a Yellow Sun", "Americanah"],
},
];
let books = [
{
id: "1",
name: "To Kill a Mockingbird",
pages: 281,
title: "Harper Lee",
price: 12.99,
},
{
id: "2",
name: "The Catcher in the Rye",
pages: 224,
title: "J.D. Salinger",
price: 9.99,
},
{
id: "3",
name: "The Little Prince",
pages: 85,
title: "Antoine de Saint-Exupéry",
price: 7.99,
},
];
app.get("/current-user", (req, res) => res.json(currentUser));
app.get("/users/:id", (req, res) => {
const { id } = req.params;
res.json(users.find((user) => user.id === id));
});
app.get("/users", (req, res) => res.json(users));
app.post("/users/:id", (req, res) => {
const { id } = req.params;
const { user: editedUser } = req.body;
users = users.map((user) => (user.id === id ? editedUser : user));
res.json(users.find((user) => user.id === id));
});
app.get("/books", (req, res) => res.json(books));
app.get("/books/:id", (req, res) => {
const { id } = req.params;
res.json(books.find((book) => book.id === id));
});
let SERVER_PORT = 9090;
app.listen(SERVER_PORT, () =>
console.log(`Server is listening on port: ${SERVER_PORT}`)
); 通用用户信息容器组件 UserLoader
import axios from "axios";
import React, { useEffect, useState, ReactElement } from "react";
// 定义 User 类型
type User = {
name: string;
age: number;
country: string;
books: string[];
};
// 定义 UserLoaderProps 类型
type UserLoaderProps = {
userId: string;
children: ReactElement<{ user: User | null }>;
};
// `UserLoader` 组件
export const UserLoader = ({
userId,
children,
}: UserLoaderProps): ReactElement => {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
(async () => {
const response = await axios.get(`/api/users/${userId}`);
setUser(response.data);
})();
}, [userId]);
return (
<>
{React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { user });
}
return child;
})}
</>
);
}; Appimport { UserInfo } from "./components/user-info";
import { UserLoader } from "./components/user-loader";
function App() {
return (
<>
<UserLoader userId={"1"}>
<UserInfo />
</UserLoader>
<UserLoader userId={"2"}>
<UserInfo />
</UserLoader>
<UserLoader userId={"3"}>
<UserInfo />
</UserLoader>
</>
);
}
export default App; 5. 资源数据加载组件 Loader Component for Resource Data
子组件 book-info
import React from 'react';
type Book = {
name: string;
price: number;
title: string;
pages: number;
};
type BookInfoProps = {
book?: Book;
};
export const BookInfo = ({ book }: BookInfoProps): React.ReactElement => {
const { name, price, title, pages } = book || {} as Book;
return book ? (
<>
<h3>{name}</h3>
<p>{price}</p>
<h3>Title: {title}</h3>
<p>Number of Pages: {pages}</p>
</>
) : (
<h1>Loading</h1>
);
}; 子组件
import React from 'react';
// 定义 User 类型
type User = {
name: string;
age: number;
country: string;
books: string[];
};
// 定义组件的 Props 类型
type UserInfoProps = {
user?: User;
};
export const UserInfo= ({ user }:UserInfoProps) : React.ReactElement=> {
const { name, age, country, books } = user || {} as User;
return user ? (
<>
<h2>{name}</h2>
<p>Age: {age} years</p>
<p>Country: {country}</p>
<h2>Books</h2>
<ul>
{books.map((book) => (
<li key={book}>{book}</li>
))}
</ul>
</>
) : (
<h1>Loading...</h1>
);
}; 通用资源容器组件 resource-loader.
import axios from "axios";
import React, {
useEffect,
useState,
ReactNode,
ReactElement,
cloneElement,
} from "react";
type ResourceLoaderProps = {
resourceUrl: string;
resourceName: string;
children: ReactNode;
};
export const ResourceLoader = ({
resourceUrl,
resourceName,
children,
}: ResourceLoaderProps): ReactElement => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [resource, setResource] = useState<any>(null);
useEffect(() => {
(async () => {
const response = await axios.get(resourceUrl);
setResource(response.data);
})();
}, [resourceUrl]);
return (
<>
{React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return cloneElement(child, { [resourceName]: resource });
}
return child;
})}
</>
);
};
import { BookInfo } from "./components/book-info";
import { UserInfo } from "./components/user-info";
import { ResourceLoader } from "./components/resource-loader";
function App() {
return (
<>
<ResourceLoader resourceUrl={"/api/users/1"} resourceName={"user"}>
<UserInfo />
</ResourceLoader>
<ResourceLoader resourceUrl={"/api/books/1"} resourceName={"book"}>
<BookInfo />
</ResourceLoader>
</>
);
}
export default App; 6. 数据源组件 DataSource Component
数据源容器组件,通过函数属性[替代内置的api请求]获取数据
import React, {
useEffect,
useState,
ReactNode,
ReactElement,
cloneElement,
} from "react";
type DataSourceProps<T> = {
getData: () => Promise<T>;
resourceName: string;
children: ReactNode;
};
export const DataSource = <T,>({
getData,
resourceName,
children,
}: DataSourceProps<T>): ReactElement => {
const [resource, setResource] = useState<T | null>(null);
useEffect(() => {
(async () => {
const data = await getData();
setResource(data);
})();
}, [getData]);
return (
<>
{React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return cloneElement(child, { [resourceName]: resource });
}
return child;
})}
</>
);
}; App
import axios from "axios";
import { DataSource } from "./components/data-source";
import { UserInfo, type User } from "./components/user-info";
const fetchData = async <T,>(url: string): Promise<T> => {
const response = await axios.get(url);
return response.data;
};
function App() {
return (
<>
<DataSource
getData={() => fetchData<User>("/api/users/1")}
resourceName="user"
>
<UserInfo />
</DataSource>
</>
);
}
export default App; 7. 使用渲染属性模式的容器组件 Container Component with Render Props Pattern
带有渲染的数据源容器
import React, { useEffect, useState, ReactNode } from "react";
type DataSourceWithRenderProps<T> = {
getData: () => Promise<T>;
render: (resource?: T) => ReactNode;
};
export const DataSourceWithRenderProps = <T,>({
getData,
render,
}: DataSourceWithRenderProps<T>) => {
const [resource, setResource] = useState<T>();
useEffect(() => {
(async () => {
const data = await getData();
setResource(data);
})();
}, [getData]);
return <>{render(resource)}</>;
}; user-info
import React from 'react';
// 定义 User 类型
export type User = {
name: string;
age: number;
country: string;
books: string[];
};
// 定义组件的 Props 类型
type UserInfoProps = {
user?: User;
};
export const UserInfo= ({ user }:UserInfoProps) : React.ReactElement=> {
const { name, age, country, books } = user || {} as User;
return user ? (
<>
<h2>{name}</h2>
<p>Age: {age} years</p>
<p>Country: {country}</p>
<h2>Books</h2>
<ul>
{books.map((book) => (
<li key={book}>{book}</li>
))}
</ul>
</>
) : (
<h1>Loading...</h1>
);
}; App
import axios from "axios";
import { DataSourceWithRenderProps } from "./components/data-source-with-render-props";
import { UserInfo, type User } from "./components/user-info";
const fetchData = async <T,>(url: string): Promise<T> => {
const response = await axios.get(url);
return response.data;
};
function App() {
return (
<>
<DataSourceWithRenderProps<User>
getData={() => fetchData<User>("/api/users/1")}
render={(resource) => <UserInfo user={resource } />}
/>
</>
);
}
export default App; 8. 本地存储数据加载组件 Local Storage Data Loader Component
import axios from "axios";
import { DataSource } from "./components/data-source";
import { UserInfo, type User } from "./components/user-info";
const fetchData = async <T,>(url: string): Promise<T> => {
const response = await axios.get(url);
return response.data;
};
const getDataFromLocalStorage = (key: string) => (): string | null => {
return localStorage.getItem(key);
};
type MessageProps = {
msg?: string ;
};
const Message = ({ msg }: MessageProps): React.ReactElement => <h1>{msg}</h1>;
function App() {
return (
<>
<DataSource
getData={() => fetchData("/api/users/1")}
resourceName={"user"}
>
<UserInfo />
</DataSource>
<DataSource
getData={async () => getDataFromLocalStorage("test")}
resourceName={"msg"}
>
<Message />
</DataSource>
</>
);
}
export default App; |
5. 设计模式-高阶组件 Design Patterns HOCs1. 介绍 IntroductionReact设计模式:高阶组件。 高阶组件(简称HOC)是一些组件,它们不是直接返回JSX,而是返回另一个组件。 大多数React组件只是返回JSX,这些JSX代表将要渲染的DOM元素。 然而,通过高阶组件,我们引入了一个额外的层次,HOC不会直接返回JSX,而是返回另一个组件,这个组件再返回JSX。 为了简化这个概念,记住高阶组件本质上是返回组件的函数。 你可以把它们看作是组件工厂,当这些函数被调用时,它们会生成新的组件。 这种思维模型将帮助你掌握HOC的本质。 那么,为什么要创建高阶组件呢? 原因有几个。首先,HOC使我们能够在多个组件之间共享行为。 这类似于我们在容器组件中看到的,不同的组件被包装在同一个容器中,并表现出相似的行为。 高阶组件提供了一种实现类似功能的方法,用于共享相关的逻辑。 此外,高阶组件允许我们为现有组件添加额外功能。 如果我们遇到一个现有的组件,比如由其他人开发的遗留代码,HOC提供了一种方法,可以在不修改原始代码的情况下,为该组件增加新的功能和特性。 在本章的示例中,我们将更详细地探讨这些情况,展示高阶组件如何增强代码重用性和扩展组件功能。 2. 使用高阶组件检查属性 Checking Props with HOC
import React from 'react';
export const checkProps = <P extends object>(Component: React.ComponentType<P>) => {
return (props: P) => {
console.log(props);
return <Component {...props} />;
};
};
import React from 'react';
// 定义 User 类型
export type User = {
name: string;
age: number;
country: string;
books: string[];
};
// 定义组件的 Props 类型
export type UserInfoProps = {
user?: User;
};
export const UserInfo= ({ user }:UserInfoProps) : React.ReactElement=> {
const { name, age, country, books } = user || {} as User;
return user ? (
<>
<h2>{name}</h2>
<p>Age: {age} years</p>
<p>Country: {country}</p>
<h2>Books</h2>
<ul>
{books.map((book) => (
<li key={book}>{book}</li>
))}
</ul>
</>
) : (
<h1>Loading...</h1>
);
};
import React from 'react';
import { checkProps } from './components/check-props';
import { UserInfo, type UserInfoProps } from './components/user-info';
const UserInfoWrapper = checkProps<UserInfoProps>(UserInfo);
function App() {
return (
<>
<UserInfoWrapper user={{
name: "Sarah Waters",
age: 55,
country: "United Kingdom",
books: ["Fingersmith", "The Night Watch"]
}} />
</>
);
}
export default App; 3. 使用高阶组件加载数据 Data Loading with HOC
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { User } from './user-info';
export const includeUser = <P extends object>(Component: React.ComponentType<P & { user?: User }>, userId: string) => {
return (props: P) => {
const [user, setUser] = useState<User | undefined>(undefined);
useEffect(() => {
const fetchUser = async () => {
try {
const response = await axios.get(`/api/users/${userId}`);
setUser(response.data);
} catch (error) {
console.error('获取用户数据时出错:', error);
setUser(undefined); // 或者根据需要处理错误状态
}
};
fetchUser();
}, []); // 不依赖于 userId
return <Component {...props} user={user} />;
};
};
import { includeUser } from "./components/include-user";
import { UserInfo } from "./components/user-info";
const UserInfoWithUser = includeUser(UserInfo, "2");
function App() {
return (
<>
<UserInfoWithUser />
</>
);
}
export default App; 4 使用高阶组件更新数据 Updating Data with HOC
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { User } from './user-info';
type IncludeUpdatableUserProps = {
updatableUser: User | null;
changeHandler: (updates: Partial<User>) => void;
userPostHandler: () => Promise<void>;
resetUserHandler: () => void;
};
export const includeUpdatableUser = <P extends object>(Component: React.ComponentType<P & IncludeUpdatableUserProps>, userId: string) => {
return (props: P) => {
const [user, setUser] = useState<User | null>(null);
const [updatableUser, setUpdatableUser] = useState<User | null>(null);
useEffect(() => {
(async () => {
const response = await axios.get(`/api/users/${userId}`);
setUser(response.data);
setUpdatableUser(response.data);
})();
}, [userId]);
const userChangeHandler = (updates: Partial<User>) => {
setUpdatableUser((prev) => (prev ? { ...prev, ...updates } : null));
};
const userPostHandler = async () => {
if (updatableUser) {
const response = await axios.post(`/api/users/${userId}`, {
user: updatableUser,
});
setUser(response.data);
setUpdatableUser(response.data);
}
};
const resetUserHandler = () => {
setUpdatableUser(user);
};
return (
<Component
{...props}
updatableUser={updatableUser}
changeHandler={userChangeHandler}
userPostHandler={userPostHandler}
resetUserHandler={resetUserHandler}
/>
);
};
}; 5. 使用高阶组件构建表单 Building Forms with HOC
import React from 'react';
import { includeUpdatableUser } from './include-updatable-user';
import { User } from './user-info';
type UserInfoFormProps = {
updatableUser: User | null;
changeHandler: (updates: Partial<User>) => void;
userPostHandler: () => void;
resetUserHandler: () => void;
};
export const UserInfoForm = includeUpdatableUser(
({ updatableUser, changeHandler, userPostHandler, resetUserHandler }: UserInfoFormProps) => {
const { name, age } = updatableUser || {};
return updatableUser ? (
<>
<label>
Name:
<input
value={name}
onChange={(e) => changeHandler({ name: e.target.value })}
/>
</label>
<label>
Age:
<input
value={age}
onChange={(e) => changeHandler({ age: Number(e.target.value) })}
/>
</label>
<button onClick={resetUserHandler}>Reset</button>
<button onClick={userPostHandler}>Save</button>
</>
) : (
<h3>Loading...</h3>
);
},
"3"
);
import { UserInfoForm } from "./components/user-form";
function App() {
return (
<>
<UserInfoForm updatableUser={null} changeHandler={function (): void {
throw new Error("Function not implemented.");
} } userPostHandler={function (): void {
throw new Error("Function not implemented.");
} } resetUserHandler={function (): void {
throw new Error("Function not implemented.");
} } />
</>
);
}
export default App; 6. 增强高阶组件模式 Enhancing HOC Pattern不再局限于更新用户数据,而是通过资源api和资源名称,更新通用数据。
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const toCapital = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
type IncludeUpdatableResourceProps<T> = {
[key: string]: T | ((updates: Partial<T>) => void) | (() => void);
};
export const includeUpdatableResouce = <T, P extends object>(
Component: React.ComponentType<P & IncludeUpdatableResourceProps<T>>,
resourceUrl: string,
resourceName: string
) => {
return (props: P) => {
const [data, setData] = useState<T | null>(null);
const [updatableData, setUpdatableData] = useState<T | null>(null);
useEffect(() => {
(async () => {
const response = await axios.get(resourceUrl);
setData(response.data);
setUpdatableData(response.data);
})();
}, [resourceUrl]);
const changeHandler = (updates: Partial<T>) => {
setUpdatableData((prev) => (prev ? { ...prev, ...updates } : prev));
};
const dataPostHandler = async () => {
if (updatableData) {
const response = await axios.post(resourceUrl, {
[resourceName]: updatableData,
});
setData(response.data);
setUpdatableData(response.data);
}
};
const resetHandler = () => {
setUpdatableData(data);
};
const resourceProps = {
[resourceName]: updatableData,
[`onChange${toCapital(resourceName)}`]: changeHandler,
[`onSave${toCapital(resourceName)}`]: dataPostHandler,
[`onReset${toCapital(resourceName)}`]: resetHandler,
} as IncludeUpdatableResourceProps<T>;
return <Component {...props} {...resourceProps} />;
};
};
import React from 'react';
import { includeUpdatableResouce } from './include-updatable-resouce';
import { User } from './user-info';
type UserInfoFormProps = {
user: User | null;
onChangeUser: (updates: Partial<User>) => void;
onSaveUser: () => void;
onResetUser: () => void;
};
export const UserInfoForm = includeUpdatableResouce<User, UserInfoFormProps>(
({ user, onChangeUser, onSaveUser, onResetUser }: UserInfoFormProps) => {
const { name, age } = user || {};
return user ? (
<>
<label>
Name:
<input
value={name}
onChange={(e) => onChangeUser({ name: e.target.value })}
/>
</label>
<label>
Age:
<input
value={age}
onChange={(e) => onChangeUser({ age: Number(e.target.value) })}
/>
</label>
<button onClick={onResetUser}>Reset</button>
<button onClick={onSaveUser}>Save</button>
</>
) : (
<h3>Loading...</h3>
);
},
'/api/users/2',
'user'
);
import { UserInfoForm } from "./components/user-form";
function App() {
return (
<>
<UserInfoForm user={null} onChangeUser={function (): void {
throw new Error("Function not implemented.");
} } onSaveUser={function (): void {
throw new Error("Function not implemented.");
} } onResetUser={function (): void {
throw new Error("Function not implemented.");
} } />
</>
);
}
export default App; |
7. React中的函数式编程设计模式 Design Patterns Functional Programming in React1. 介绍函数式编程是一种组织代码的方法,它强调最小化变异和状态变化,利用独立于外部数据的纯函数,并将函数视为一等公民。 虽然这个定义最初可能看起来有点晦涩,但如果你是函数式编程的新手,请不要慌张。我建议你做一些相关研究,因为这可以在你的开发者职业生涯中对你有很大帮助。 一个常见的应用是在控制组件中,我们之前已经讨论过。控制组件允许我们通过传递必要的属性来管理组件状态,最小化组件对内部状态管理的依赖。 函数组件是 React 中函数式编程的另一个关键应用。与已经存在一段时间的类组件不同,函数组件体现了函数式编程范式,提供了一种简洁明了的定义组件的方法。 高阶组件(HOCs)是 React 中函数式编程的另一个例子,在本课程中我们已经探索过它们。HOCs 利用一等函数的概念,创建返回其他函数的可重用函数,提供强大的功能和组合能力。 接下来,我们将深入探讨另外三种设计模式,这些模式展示了函数式编程在 React 中的影响:递归组件、部分应用组件和组件组合。 递归组件依赖于递归来实现特定效果。它们可以非常强大,提供复杂问题的独特解决方案。请务必关注这一部分内容,它非常重要。 部分应用组件通过传递组件属性的一个子集来创建更具体的通用组件版本。这种技术允许代码重用和组件定制的灵活性。 最后但同样重要的是,组件组合涉及将多个组件组合成一个单一组件以实现所需效果。这种模式允许通过组合更简单的组件来创建更复杂的组件。 当我们探索这些设计模式时,我们看到函数式编程原则如何增强 React 应用程序的模块化、可重用性和可维护性。 2. 递归组件 Recursive Components
const isValidObj = (data: string | object) =>
typeof data === "object" && data !== null;
export const Recursive = ({ data }: { data: string | object }) => {
if (!isValidObj(data)) {
return <li>{data}</li>;
}
const pairs = Object.entries(data);
console.log(data);
return (
<>
{pairs.map(([key, value]) => {
return (
<li key={key}>
{key}:
<ul>
<Recursive data={value} />
</ul>
</li>
);
})}
</>
);
};
import { Recursive } from "./components/recursive";
import "./App.css";
const myNestedObject = {
key1: "value1",
key2: {
innerKey1: "innerValue1",
innerKey2: {
innerInnerKey1: "innerInnerValue1",
innerInnerKey2: "innerInnerValue2",
},
},
key3: "value3",
};
function App() {
return (
<>
<Recursive data={myNestedObject} />
</>
);
}
export default App; 3. Compositions 组合组件[类似继承]
import React from "react";
type ButtonProps = {
size?: "small" | "large";
color?: string;
text: string;
};
export const Button: React.FC<ButtonProps> = ({
size,
color,
text,
...props
}) => {
return (
<button
style={{
fontSize: size === "large" ? "25px" : "16px",
backgroundColor: color,
}}
{...props}
>
{text}
</button>
);
};
export const SmallButton: React.FC<ButtonProps> = (props) => {
return <Button size="small" {...props} />;
};
export const SmallRedButton: React.FC<ButtonProps> = (props) => {
return <SmallButton size={"large"} color="crimson" {...props} />;
};
import "./App.css";
import { SmallButton, SmallRedButton } from "./components/composition";
function App() {
return (
<>
<SmallButton text={"I am small!"} />
<SmallRedButton text={"I am small and Red"} />
</>
);
}
export default App; 4. Partial Components 部分模式只是用组件的一部分
import React from "react";
type ButtonProps = {
size?: "small" | "large";
color?: string;
text?: string;
};
// 定义高阶组件partial的类型
export function partial<T>(
Component: React.ComponentType<T>,
partialProps: Partial<T>
) {
return (props: T): JSX.Element => {
return <Component {...partialProps} {...props} />;
};
}
export const Button: React.FC<ButtonProps> = ({
size,
color,
text,
...props
}) => {
return (
<button
style={{
fontSize: size === "large" ? "25px" : "16px",
backgroundColor: color || "initial",
}}
{...props}
>
{text}
</button>
);
};
// 使用partial创建SmallButton
export const SmallButton = partial(Button, { size: "small" });
// 使用partial创建LargeRedButton
export const LargeRedButton = partial(Button, {
size: "large",
color: "crimson",
});
import { LargeRedButton, SmallButton } from "./components/partial";
function App() {
return (
<>
<SmallButton text={"I am small!"}/>
<LargeRedButton text="I am large and Red"/>
</>
);
}
export default App; |
8. Design Patterns More Patterns1. Compound Components 复合组件
import React, { createContext, useContext } from "react";
// 定义Context的类型
interface ContextType {
test?: string;
}
// 创建带有初始值的Context
const Context = createContext<ContextType | null>(null);
type Props = {
children: React.ReactNode;
};
// Body组件
const Body: React.FC<Props> = ({ children }) => {
return <div style={{ padding: ".5rem" }}>{children}</div>;
};
// Header组件
const Header: React.FC<Props> = ({ children }) => {
const context = useContext(Context);
return (
<div
style={{
borderBottom: "1px solid black",
padding: ".5rem",
marginBottom: ".5rem",
}}
>
{children}
{/* 从context中安全地获取test值 */}
{context?.test}
</div>
);
};
// Footer组件
const Footer: React.FC<Props> = ({ children }) => {
return (
<div
style={{
borderTop: "1px solid black",
padding: ".5rem",
marginTop: ".5rem",
}}
>
{children}
</div>
);
};
type CardProps = {
test?: string;
children: React.ReactNode;
};
// Card组件
const Card: React.FC<CardProps> & {
Header: typeof Header;
Body: typeof Body;
Footer: typeof Footer;
} = ({ test, children }) => {
return (
<Context.Provider value={{ test }}>
<div style={{ border: "1px solid black" }}>{children}</div>
</Context.Provider>
);
};
Card.Header = Header;
Card.Body = Body;
Card.Footer = Footer;
export default Card;
import Card from "./components/card";
function App() {
return (
<Card test="Value">
<Card.Header>
<h1 style={{ margin: "0" }}>Header</h1>
</Card.Header>
<Card.Body>
He hid under the covers hoping that nobody would notice him there. It
really didn't make much sense since it would be obvious to anyone who
walked into the room there was someone hiding there, but he still held
out hope. He heard footsteps coming down the hall and stop in front in
front of the bedroom door. He heard the squeak of the door hinges and
someone opened the bedroom door. He held his breath waiting for whoever
was about to discover him, but they never did.
</Card.Body>
<Card.Footer>
<button>Ok</button>
<button>Cancel</button>
</Card.Footer>
</Card>
);
}
export default App; 2. Observer Pattern 观察员模式pnpm i mitt -S
import { emitter } from "../App";
const Buttons = (props) => {
const onIncrementCounter = () => {
emitter.emit("increment");
};
const onDecrementCounter = () => {
emitter.emit("decrement");
};
return (
<div>
<button onClick={onIncrementCounter}>➕</button>
<button onClick={onDecrementCounter}>➖</button>
</div>
);
};
export default Buttons;
import { useEffect, useState } from "react";
import { emitter } from "../App";
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const onIncrement = () => {
setCount((count) => count + 1);
};
const onDecrement = () => {
setCount((count) => count - 1);
};
emitter.on("increment", onIncrement);
emitter.on("decrement", onDecrement);
return () => {
emitter.off("increment", onIncrement);
emitter.off("decrement", onDecrement);
};
}, []);
return <div>#: {count}</div>;
};
export default Counter;
import Buttons from "./buttons";
import Counter from "./counter";
const ParentComponent = (props) => {
return (
<>
<Buttons />
<Counter />
</>
);
};
export default ParentComponent;
import ParentComponent from "./components/parent";
import mitt from "mitt";
export const emitter = mitt();
function App() {
return (
<>
<ParentComponent />
</>
);
}
export default App; |
10. Clean Code Tips 清洁代码技巧 //todo 此章以后都需要实际验证1. Using Element Prop 使用元素属性问题一:调整按钮样式和链接标签我们有一组按钮,可以通过传递自定义属性(props)来设置大小,例如:小号、大号、超大号等。这里还有一个孤立的链接标签(anchor tag)。如果我们想让它看起来像这些按钮之一,使用当前代码并不容易实现。
解决方案:使用
|
11. Scalable Project Architecture 可扩展项目架构1. General Architecture 一般架构在本章中,我们将讨论 React 项目的目录结构,具体来说是 目录结构解释
|
12. API Layer and Async Operations API层和异步操作1.Building an API Layer 构建API层为了提高代码的可维护性和清晰度,我们可以在组件和后端服务器之间建立一个API层。通过这种方式,组件不需要直接调用后端服务器,而是通过API层与后端通信。以下是具体实现步骤: 创建API层
为特定功能创建API文件对于不同的功能(例如用户、产品等),我们可以为每个功能创建单独的API文件,使代码更具模块化。以下是一个用户API文件的示例: // src/api/userApi.js
import api from './api';
const userApi = {
fetchUsers: () => api.get('/users'),
};
export default userApi; 创建组件以使用API文件接下来,我们将创建一个用户列表组件,使用自定义钩子来处理数据获取逻辑: // src/components/Users.js
import React, { useEffect, useState } from 'react';
import userApi from '../api/userApi';
const useFetchUsers = () => {
const [users, setUsers] = useState([]);
const initFetchUsers = async () => {
const response = await userApi.fetchUsers();
setUsers(response.data);
};
return { users, initFetchUsers };
};
const Users = () => {
const { users, initFetchUsers } = useFetchUsers();
useEffect(() => {
initFetchUsers();
}, []);
return (
<div>
<button onClick={initFetchUsers}>Fetch Users</button>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name} - {user.email}</li>
))}
</ul>
</div>
);
};
export default Users; 在应用中使用组件将 // src/App.js
import React from 'react';
import Users from './components/Users';
function App() {
return (
<div className="App">
<h1>User List</h1>
<Users />
</div>
);
}
export default App; 这种API层结构的主要优点包括:
通过这种方式,我们将API请求封装在特定的功能模块中,使得组件代码更简洁,项目结构更清晰。 2. API States API状态在应用程序中处理异步 API 调用时,通常会遇到不同的状态——例如加载中、成功、失败等,以便在用户交互时提供反馈。为了编写更简洁的代码和提升用户体验,可以采用更加简化的方法来管理这些状态。以下是高效实现这些状态管理的步骤: 实现步骤1. 定义 API 状态可以使用一个单一的 const API_STATUS = {
IDLE: 'idle',
PENDING: 'pending',
SUCCESS: 'success',
ERROR: 'error',
}; 2. 创建
|
13. API Layer with React-Query 使用React-Query的API层1. Server Setup and a Quick Fix to withLogger Function 服务器设置和withLogger函数的快速修复欢迎回来! 在上一节中,我们讨论了如何处理API请求,以及如何使用API层和我们创建的自定义钩子(如 在继续之前,先提两点首先,在上一节视频中,我们在API文件中创建了一个 第二,为了演示,我们还搭建了一个用Express.js构建的小型服务器。虽然涉及一些后端内容,但它相对简单,可以帮助您更好地理解整个流程。这个代码可以在 如果您在设置过程中遇到问题或有疑问,请随时与我联系,我会非常乐意回答您的问题。 2. Fetching Data with React-Query 使用React-Query获取数据好的,那么我们开始用React Query来与我们的API层进行整合,首先创建一个简单的组件来展示一些名言。 第一步在 // quoteAPI.js
import API from './API';
export const fetchTopQuotes = () => {
return API.get('/top_quotes').then((res) => res.data.quotes);
}; 第二步创建一个名为 // TopQuotes.jsx
import React from 'react';
import { useQuery } from 'react-query';
import { fetchTopQuotes } from './quoteAPI';
const TopQuotes = () => {
const { data: quotes, isLoading, isError } = useQuery('topQuotes', fetchTopQuotes);
if (isLoading) return <p>Loading...</p>;
if (isError) return <p>Error fetching quotes</p>;
return (
<div>
<h2>Top Quotes</h2>
<ul>
{quotes.map((quote, index) => (
<li key={index}>{quote}</li>
))}
</ul>
</div>
);
};
export default TopQuotes; 第三步将该组件集成到主 // App.js
import React from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
import TopQuotes from './TopQuotes';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<div className="App">
<TopQuotes />
</div>
</QueryClientProvider>
);
}
export default App; 额外功能若有需要,也可以添加错误提示和加载状态的样式。将加载提示、错误提示等通过条件渲染展示出来,提升用户体验。 至此,我们已经成功完成了React Query与API层的结合!这样可以更方便地处理数据获取、缓存和状态管理。 3. Updating Data with React-Query 使用React-Query更新数据要更新数据到服务器,可以使用React Query提供的 第一步在
// quoteAPI.js
import API from './API';
export const postQuote = (quote) => {
return API.post('/add_quote', quote);
};
export const resetQuotes = () => {
return API.post('/reset', {});
}; 第二步创建一个名为 // UpdateQuote.jsx
import React, { useState } from 'react';
import { useMutation, useQueryClient } from 'react-query';
import { postQuote, resetQuotes } from './quoteAPI';
const UpdateQuote = () => {
const queryClient = useQueryClient();
const [form, setForm] = useState({ author: '', quote: '' });
const createQuoteMutation = useMutation(postQuote, {
onSuccess: () => {
queryClient.invalidateQueries('topQuotes');
setForm({ author: '', quote: '' });
alert('Quote created successfully');
},
});
const resetQuotesMutation = useMutation(resetQuotes, {
onSuccess: () => {
queryClient.invalidateQueries('topQuotes');
alert('Quotes reset successfully');
},
});
const handleChange = (e) => {
const { name, value } = e.target;
setForm((prev) => ({ ...prev, [name]: value }));
};
const handleSubmit = (e) => {
e.preventDefault();
if (form.author && form.quote) {
createQuoteMutation.mutate(form);
} else {
alert('Please fill in all fields');
}
};
const handleReset = () => {
resetQuotesMutation.mutate();
};
return (
<div>
<h2>Update Quotes</h2>
<form onSubmit={handleSubmit}>
<div>
<label>Author:</label>
<input type="text" name="author" value={form.author} onChange={handleChange} />
</div>
<div>
<label>Quote:</label>
<input type="text" name="quote" value={form.quote} onChange={handleChange} />
</div>
<button type="submit">Add Quote</button>
<button type="button" onClick={handleReset}>Reset Quotes</button>
</form>
</div>
);
};
export default UpdateQuote; 第三步在 // App.js
import React from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
import TopQuotes from './TopQuotes';
import UpdateQuote from './UpdateQuote';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<div className="App">
<UpdateQuote />
<TopQuotes />
</div>
</QueryClientProvider>
);
}
export default App; 总结这样我们实现了通过React Query的 4. Pagination with React-Query 使用React-Query分页要实现分页功能,可以使用React Query中的 第一步:在API文件中添加分页功能在 // quoteAPI.js
import API from './API';
export const fetchQuotesByPage = (page) => {
return API.get('/quotes', {
params: { page },
}).then((res) => res.data);
}; 第二步:创建分页组件接下来,在项目中创建一个新的组件 // PaginatedQuotes.jsx
import React, { useState } from 'react';
import { useQuery } from 'react-query';
import { fetchQuotesByPage } from './quoteAPI';
const PaginatedQuotes = () => {
const [page, setPage] = useState(1);
const { data, isLoading, isError, isPreviousData } = useQuery(
['quotes', page],
() => fetchQuotesByPage(page),
{ keepPreviousData: true }
);
return (
<div>
<h2>Quotes - Page {page}</h2>
{isLoading ? (
<p>Loading quotes...</p>
) : isError ? (
<p>Error fetching quotes.</p>
) : (
<div>
{data.quotes.map((quote) => (
<p key={quote.id}>{quote.text} - {quote.author}</p>
))}
<button
onClick={() => setPage((old) => Math.max(old - 1, 1))}
disabled={page === 1}
>
Previous
</button>
<button
onClick={() => {
if (!isPreviousData && data.hasMore) {
setPage((old) => old + 1);
}
}}
disabled={isPreviousData || !data?.hasMore}
>
Next
</button>
</div>
)}
</div>
);
};
export default PaginatedQuotes; 代码说明:
第三步:在App.js中导入并使用分页组件将新的 // App.js
import React from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
import PaginatedQuotes from './PaginatedQuotes';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<div className="App">
<PaginatedQuotes />
</div>
</QueryClientProvider>
);
}
export default App; 运行效果页面加载时, 5. Infinite scroll with React-Query 使用React-Query无限滚动为了实现无限滚动,你可以结合React Query的 第一步:创建一个按游标分页获取数据的函数在你的API文件 // quoteAPI.js
import API from './API';
export const fetchQuotesByCursor = (cursor) => {
return API.get('/quotes', {
params: { cursor },
}).then((res) => res.data);
}; 第二步:创建无限滚动组件接下来,创建一个新的组件 // InfiniteScrollQuotes.jsx
import React, { useEffect } from 'react';
import { useInfiniteQuery } from 'react-query';
import { fetchQuotesByCursor } from './quoteAPI';
import { useInView } from 'react-intersection-observer';
const InfiniteScrollQuotes = () => {
const { ref, inView } = useInView();
const {
data,
isLoading,
isError,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery(
'quotes',
({ pageParam = null }) => fetchQuotesByCursor(pageParam),
{
getNextPageParam: (lastPage) => lastPage.nextCursor ?? false,
}
);
useEffect(() => {
if (inView && hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]);
return (
<div>
<h2>Infinite Scroll Quotes</h2>
{isLoading ? (
<p>Loading quotes...</p>
) : isError ? (
<p>Error fetching quotes.</p>
) : (
<div>
{data.pages.map((page, index) => (
<React.Fragment key={index}>
{page.quotes.map((quote) => (
<p key={quote.id}>{quote.text} - {quote.author}</p>
))}
</React.Fragment>
))}
<div ref={ref} style={{ height: '20px' }}>
{isFetchingNextPage ? 'Loading more...' : 'Load more'}
</div>
</div>
)}
</div>
);
};
export default InfiniteScrollQuotes; 代码说明:
第三步:在App.js中使用无限滚动组件将新的 // App.js
import React from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
import InfiniteScrollQuotes from './InfiniteScrollQuotes';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<div className="App">
<InfiniteScrollQuotes />
</div>
</QueryClientProvider>
);
}
export default App; 运行效果此时,当用户滚动到底部时,组件将自动加载更多数据。随着用户不断向下滚动,新的数据将被自动加载,实现了无限滚动效果。 6. Query Cancellation with React-Query 使用React-Query取消查询要在React Query中实现请求取消,可以利用Axios中的 第一步:在API文件中使用
|
14. State Management Patterns 状态管理模式1. Immutable updates with useImmer 使用useImmer进行不可变更新欢迎回来!正如你所知,在 React 代码库中,大多数状态可以通过 使用
|
15. Performance Optimization 性能优化1. Code-Splitting and Lazy-Loading 代码分割和懒加载在提升应用程序加载性能方面,有一个非常有效的技术:代码分割和延迟加载。 代码分割与延迟加载的好处过去,我们通常会将整个应用程序的 JavaScript 代码打包成一个大文件,但这种方式会导致用户在访问某个页面时,必须下载整个网站的代码,即便他们不需要访问所有页面。这种情况在应用程序包含多个页面和复杂功能时尤为明显。用户可能在使用网站之前被迫下载大量代码,这不仅增加了等待时间,也提高了用户离开网站的概率。 现代应用程序通常将代码分成多个小块,这样用户在访问某个页面时,只会下载与该页面相关的代码。访问其他页面时,所需代码才会按需加载,从而缩短加载时间。 React 中的代码分割与延迟加载在 React 应用中,我们可以使用 基本设置
使用
|
16. Design System Core Concepts 设计系统核心概念1. What is a design system 什么是设计系统其实,并没有一个确切的定义来描述什么是设计系统。那么,它到底意味着什么呢? 如果是 UI/UX 设计师谈论设计系统,他们很可能指的是设计语言,比如色彩搭配、排版以及 UI 工具包,比如 Figma 等工具。而如果是开发人员谈论设计系统,他们通常指的是像 React、Angular 等 Web 框架中的组件库,或通过 Gatsby、Storybook 等工具实现的样式指南。 实际上,设计系统包括所有这些组成部分:设计语言、UI 工具包、组件库和样式指南。在本课程中,我会带你深入了解所有这些主题,帮助你掌握如何设计和构建一个整洁而稳固的设计系统。 2. The importance of having a design system 拥有设计系统的重要性设计系统帮助公司定义品牌身份,并将其转化为可访问、一致且可重用的组件,这带来了许多好处。 首先,设计系统确保所有用户,无论其状况如何,都能像其他人一样使用你的产品。设计系统确保应用程序有足够的颜色对比度,适当的文字比例和较大的字号,使内容易于浏览。此外,它确保应用程序的各个部分可以通过键盘访问,且重要的提示能被屏幕阅读器检测到,以帮助需要这些功能的用户。 在为企业公司工作时,通常会有一系列产品,像是“兄弟姐妹”一般。一个常见的例子就是 Google,他们有一整套看起来像同一家族的产品。无论你使用 Google Drive 还是 Google Maps,界面元素总是保持一致,比如按钮等。这就是设计系统中的一致性,所有产品都应展示公司的品牌。设计系统的首要任务之一就是确保这种一致性。 设计系统的另一个重要好处是支持渐进式更新。有了设计系统,不再需要频繁的小规模更新,也不需要跨团队沟通来通知每个团队关于新样式更新的详细信息。你只需一次更改,整个设计系统就会根据更改自动更新。 此外,设计系统对于新成员的入职也非常有帮助,它提供了一个中心样式指南,作为一站式参考,帮助团队中新加入的前端开发人员迅速找到开始工作所需的信息。这对于跨团队协作也十分有利,因为所有团队都在使用相同的系统,开发人员可以轻松地参与到其他团队的项目中,比如 A 团队的开发人员可以轻松地帮助 B 团队修复一个 bug,因为他们使用的是同一个设计系统。 当设计系统稳定运行时,设计师可以利用 UI 工具包快速创建新组件的原型,随后开发人员也可以使用组件库,以极快的速度开发这些新组件。因为设计师和开发人员都已经有了所需的组件和设计,只需调用这些资源即可,以快速实现产品中的新功能。 3. Down sides of design systems 设计系统的缺点好的,在我们刚才讨论了设计系统的诸多优点之后,也需要考虑这些系统可能带来的一些缺点。 首先是时间问题。从零开始构建一个稳固的设计系统可能需要较长时间,有时甚至需要数年。而对公司来说,时间是一种非常宝贵的资源。要解决这个问题,需要设立合适的设计系统团队结构。别担心,我们将在后续的讲解中详细讨论不同的团队结构,帮助你选择合适的团队来应对时间问题。 设计系统的开发过程是持续的。它的生命周期与产品的存在时间一样长,并会随时间不断进化。这是因为设计系统本质上是服务于其他产品的产品。 接下来是维护问题。每个产品都需要维护,设计系统也不例外。它作为服务于其他产品的产品,需要一个团队来持续维护。向利益相关者说明设计系统需要大量初期资源,他们可能不会太乐意接受,但这是确保成功的必要条件。你将需要设计师、工程师,甚至产品经理来维护设计系统。 考虑到上述内容,如果问我什么问题可能会导致设计系统失败,我的答案并不是时间或维护,而是适应性。要确保设计系统成功,你需要一个适当的团队结构,以便能够说服所有产品团队使用并遵循设计系统。因此,若你想让所有相关产品团队采纳你的设计系统,就需要制定最优的团队结构策略。在下一节视频中,我们将深入探讨这个话题。 4. Team Structure 团队结构我们刚才讨论了设计系统的一些缺点,并提到了解决部分问题需要适当的团队结构。例如,设计系统是否被广泛接受对其成功至关重要。要推广设计系统,必须向公司内的所有团队成员和产品团队推销它。为此,你可以通过三种不同的方式来组建团队: 1. 集中团队 在这种模式下,有一个核心团队负责一切。他们从零开始构建和维护设计系统,包括定义系统的基础、创建UI工具包、开发组件库和风格指南。他们是唯一的负责人,外部人员无法参与其中。这个团队通常由设计师、工程师和产品经理组成。 2. 分布式团队 这种模式与集中团队相反,设计系统的消费者团队负责开发和维护系统。人们通常更喜欢参与自己帮助构建的系统,因此这种分布式模式可能会提高设计系统的受欢迎程度,进而促进更广泛的采用。此外,系统会从组织的各个角落获得创意和贡献,带来更多创新。由于多个团队共同维护系统,一个成员的缺席不会影响系统的整体进展,其他团队可以继续贡献。 3. 混合模式 这种模式结合了前两种模式的优点。它既有一个专注于设计系统的核心团队,能够加速开发和交付,还欢迎产品团队的贡献,从而带来更多创新。因此,这种模式既具备速度,也具备创新能力。 5. Audience of design systems 设计系统的受众本节课的重点是:不要直接复制粘贴开源设计系统,因为设计系统的主要受众是其最初创建的团队和公司。它是为了体现他们的品牌和身份。 当一个设计系统开源或与公司外的成员共享时,其目的是欢迎外部成员的贡献,同时也为他人提供学习和灵感。因此,你需要自己动手,根据产品的特点,从头开始构建专属于你的设计系统。 目前大多数资源会教你如何在 Figma 中创建 UI 工具包,或在 React 中基于某些预构建的设计模板开发组件库,但这种方法是不对的。首先,你需要了解如何根据特定品牌构建和发展设计语言与基础,接下来,Figma、React、Gatsby.js 和 Storybook 等工具的使用将变得相对简单。 因此,我特别为这些概念专门设计了两个独立的部分来教你缺失的关键知识。请不要跳过这些内容,尽你所能认真学习。 6. A real-life example 现实生活中的例子这一课的目的,是通过一个案例分析,让你了解设计系统的重要性,以及它如何节省时间、精力和金钱。 我们都熟悉按钮。它们看起来简单,易于开发,对吧?但事实是,开发一个按钮组件其实具有相当的复杂性和挑战。按钮是应用中最常用的组件之一,因此它们能够很好地展示你的品牌。 首先,你需要考虑按钮的属性,例如:内边距、字体、大小、颜色、字体类型等等。然后,还要考虑不同的状态,如悬停、激活、点击和禁用。通常,我们在代码中至少会有两种类型的按钮:主按钮和次按钮,甚至还可能有第三种类型的按钮。也许还需要带图标的按钮,有文字的或无文字的。每一种类型的按钮也可能会有主要和次要的两种样式。另外,不要忘了按钮的大小可能不同,比如小号和大号。除此之外,别忘了主题!如果有浅色主题和深色主题,那么所有按钮都需要相应的双份设计。 这是一项庞大的清单,对吧?现在,我们来算一算成本。假设我们有一个产品团队,包括一位设计师、一位工程师和一位质量保证工程师,他们共同负责按钮组件的设计、开发和测试。每人时薪100美元,每人完成150小时工作量,也就是每人平均花费约50小时在按钮上,这将使团队的成本总计约15,000美元。 15,000美元,仅用于开发按钮组件?听起来很夸张,但这只是一个团队的成本。假设在一家大型公司中,有50个这样的团队都在为各自的产品开发按钮,那么总成本可能会超过80万美元,甚至达到100万美元。而每个团队的按钮设计风格和质量可能都不尽相同。 这时,设计系统就派上了用场。设计系统能够将所有产品团队的设计统一起来,提供一个设计的单一来源,极大地降低成本。 7. The key concepts of design systems 设计系统的关键概念所谓的设计语言,是一组标准和元素,通过产品来定义品牌的身份。可以将设计语言视为组件、品牌以及相关设计元素的“个性”。 设计语言包括两个部分:基础和组件。基础部分包含一些原则,用来展示品牌的个性,包括颜色、字体、网格布局、图标和一些图形动画等元素。而组件库则是一些在 React.js 或 Vue.js 等框架和库中开发的组件,它们将你的设计语言和 UI 工具包转化为实际的成品。 风格指南是设计系统的文档化内容,涵盖从设计语言和 UI 工具包到组件库的所有内容。构建风格指南的工具有多种,其中 Gatsby.js 和 Storybook 是最为著名的。 8. A practical checklist 实用检查清单在将组件标记为完全交付之前,您需要进行设计和开发检查,以确保该组件满足所有要求。 设计阶段清单
开发阶段清单
通过上述检查,确保组件满足设计和开发的质量标准。 9. Mistakes to avoid 避免的错误在构建设计系统的过程中应避免的错误
|
17. Design System Building Components Using Figma 使用Figma构建组件的设计系统1. Section Overview 部分概述课程章节概述:使用 Figma 构建设计基础在本章节中,我将演示 Figma 的使用,这是目前最受欢迎的设计工具,因其具备协作设计等多项功能。在这部分课程中,我们将学习如何为项目构建设计基础,从定义色彩方案到设计组件和复杂的用户界面(UI)。
完成本章节后,您将能够熟练使用 Figma 来进行任何前端项目和设计系统的开发。 2. Hands-on Color Palette in Figma 在Figma中实际操作颜色调色板使用 Figma 构建颜色样式和调色板大家好,欢迎回来!本视频将展示如何在 Figma 网站上创建颜色调色板,并了解颜色样式的作用。
通过这些步骤,你已经学会了如何在 Figma 中创建页面、使用 Colors.co 获取配色灵感,并创建颜色样式以便在其他组件中使用。谢谢观看! 3. Hands-on Button Building Practice 按钮构建练习使用 Figma 创建按钮大家好,欢迎回来!在本节课中,我们将使用 Figma 创建应用程序的按钮样式。
祝你设计愉快! 4. Hands-on Designing a Modal 设计模态框练习在 Figma 中创建注册弹窗欢迎回来!在本节课中,我们将使用 Figma 来创建一个注册弹窗,为页面设计添加一个有用的弹窗组件。 1. 创建页面
2. 创建弹窗框架
3. 添加按钮
4. 加入图片或插图
5. 添加文本和标题
6. 添加关闭按钮
7. 最终检查和调整
完成后,你将拥有一个功能齐全、视觉一致的注册弹窗组件,便于在项目中复用! |
18. Design System Developing Components in React 在React中开发组件的设计系统1. Extensible Foundations 可扩展基础将 Figma 组件转换为 React JS 组件欢迎回来!本节课将展示如何将设计的 Figma 组件转化为 React 组件,重点关注代码的可复用性和清洁性。我们将从创建一个按钮组件开始,最终将其纳入到一个模态框中。 1. 初始化 React 项目
2. 设置全局样式
3. 创建颜色和字体比例的实用工具
4. 创建并导入 Index 文件
5. 创建按钮组件
6. 在 App 组件中应用全局样式和按钮组件
通过这些步骤,我们完成了项目的基本设置,并创建了一个带有全局样式和实用工具的基础 React 应用。接下来,我们可以继续开发其他组件,如模态框,并进一步扩展我们的设计系统。 2. Creating Button Component 创建按钮组件使用 Styled Components 创建按钮组件在本视频中,我们将使用 1. 创建按钮组件文件
2. 创建基础按钮样式
3. 为不同类型的按钮创建样式继承
4. 添加不同的状态
5. 在应用程序中使用按钮组件
通过使用 3. Building a Modal 构建模态框创建模态框组件:从 Figma 到 React.js在此视频中,我们将通过使用 1. 创建模态框组件文件
2. 定义模态框内容容器
3. 引入关闭按钮和图片
4. 创建模态框的 React 组件
5. 在
|
19. Design System Encapsulating Styles 封装样式的设计系统1. Style Compositions 样式组合样式组合:分解与复用的艺术在本视频中,我们探讨了样式组合(Style Composition)的概念,并展示了如何使用这种方法来构建更具复用性和模块化的组件库。以下是关键要点: 1. 什么是样式组合?
2. 传统方法的局限性
3. 实现样式组合的示例
4. 未来的学习内容
总结样式组合通过将样式功能抽象为小组件并加以组合,开发者可以更简洁地构建复杂布局。这种方法不仅可以减少代码重复,还能提高组件的复用性和维护性,是现代前端开发中的一种高效实践。 2. Encapsulating Styles 封装样式封装样式:提高复用性和一致性在本视频中,我们讨论了封装样式(Encapsulating Styles)的概念,并介绍了两条基本原则,帮助我们创建更具复用性和一致性的设计系统。以下是主要内容: 1. 封装的概念
2. 封装样式的两大原则原则一:组件不应该设置布局相关样式
原则二:组件应仅设置其自身及直接子组件的样式
3. 例外情况
总结通过应用封装样式的概念,我们可以创建更具复用性和一致性的组件,减少样式冲突,使得组件在不同环境中表现一致。这两条基本原则不仅能帮助我们构建稳健的设计系统,还能在日常开发中避免很多样式冲突问题。 |
20. Design System Patterns for Spacing 间距模式的设计系统1. Overview 概述间距模式:实现简单化和复用性在本节中,我们将深入探讨如何通过创建间距模式组件(Spacing Patterns Components),来简化布局中的间距处理。以下是主要内容: 1. 间距模式组件的作用
2. 使用 Style Components
3. 实现方式
总结本节课是关于实现和应用间距模式的入门课程,通过利用 2. Layers Pattern 层次模式层叠模式组件在这个视频中,我们将介绍如何构建一个称为“层叠模式”的组件,它将帮助我们轻松实现垂直布局和间距管理。以下是关键步骤和内容: 1. 什么是层叠模式组件?
2. 创建层叠模式组件
3. 通过 props 自定义间距
4. 空间方案(Space Scheme)
5. 示例:在订阅表单中使用
6. 模块化并提高复用性
总结通过这种层叠模式组件,我们可以将元素和组件垂直堆叠在一起,并控制它们之间的间距。这种模式不仅提高了代码的可读性,还确保了布局的一致性。接下来的视频中,我们将进一步探索如何构建其他实用的布局模式组件,帮助我们更好地组织页面布局。 3. Split Pattern 分割模式分割模式组件在本节中,我们将介绍如何创建“分割模式”组件,该组件通过将页面分为左右两侧,使布局更加灵活。以下是分割模式的基本内容和实现步骤: 1. 什么是分割模式?
2. 创建分割模式组件
示例代码如下: const Split = styled.div`
display: grid;
gap: ${props => spaceScheme[props.gutter] || spaceScheme.large};
grid-template-columns: 1fr 1fr;
`; 3. 自定义列比例
4. 定义预设比例方案
5. 示例:使用分割模式创建表单布局
6. 添加间距(Gap)和比例
总结分割模式组件提供了一种方便的方式来实现左右分栏布局,并允许灵活设置列宽比例和间距。通过这种模式组件,我们可以创建高度可复用的布局元素,特别适用于表单、导航等场景。在接下来的课程中,我们将继续探索其他模式组件,帮助大家掌握构建复杂布局的技巧。 4. Column Pattern 列模式列模式组件在本节中,我们将创建“列模式”组件,使布局更加灵活。该组件允许你将内容划分为多个列,并且可以控制每列的宽度和列之间的间距。 1. 什么是列模式?
2. 创建列模式组件
3. 动态调整列宽和数量
4. 定义子组件:Column
5. 自适应列的最小占比
6. 示例:创建表单布局
总结列模式组件为实现多列布局提供了灵活性,允许你动态设置列宽和列数,并且能够在各种布局需求中广泛应用。通过这种模式组件,我们可以创建高度可复用的布局元素,在不同屏幕尺寸和复杂布局中都能保持一致性。 5. Grid Pattern 网格模式网格模式组件在本节中,我们将创建一个灵活的**网格模式(Grid Pattern)**组件,这个组件可以帮助我们构建网格布局,并且可以轻松调整网格项之间的间距,支持根据屏幕宽度动态调整列的数量和项目宽度。 1. 网格模式的特点
2. 创建网格模式组件
3. 自动适应屏幕大小
4. 设置自定义宽度和间距
5. 避免布局溢出
6. 示例代码
总结通过这种网格模式组件,我们可以灵活地构建响应式网格布局,不需要手动调整媒体查询或管理各个屏幕尺寸下的显示效果。该组件简洁、实用,适用于各种应用场景中的卡片列表、商品展示等布局需求。 6. Inline-Bundle Pattern 内联捆绑模式内联组合模式组件在本节中,我们将创建一个**内联组合模式(Inline Bundle Pattern)**组件,这个组件的作用是在屏幕宽度不足时,将内联元素重新排列成多行,同时保持行内元素的布局样式。此外,我们还将允许自定义对齐和元素间的间距,以便适应不同的设计需求。 1. 内联组合模式的特点
2. 创建内联组合模式组件
3. 对齐和间距设置
4. 自定义间距
5. 示例代码
总结内联组合模式组件提供了一种简洁的解决方案来排列内容,同时保证对齐和间距的一致性。它的设计灵活,适用于菜单栏、标签列表等内容动态换行的场景。通过可配置的对齐方式和间距选项,这个组件不仅易于使用,也便于重用和扩展。 7. Inline Pattern 内联模式内联模式组件在本节中,我们将创建一个**内联模式(Inline Pattern)**组件,它的主要作用是保持子元素在同一行水平排列,同时在空间不足时,将它们垂直堆叠。相比之前的内联组合模式,这种模式还增加了组件在有限空间内自适应的功能,使其更具灵活性。 1. 内联模式的主要功能
2. 创建内联模式组件
3. 对齐和间距设置
4. 响应式调整
5. 示例代码
总结内联模式组件提供了一种灵活的方式来处理水平和垂直布局的切换,使得内容在空间充足时可以水平排列,而在空间不足时自动堆叠。通过使用 |
21. Design System Patterns for More Complex Styles 更复杂样式的设计系统模式1. Overview 概述Wrapper组件模式在本节中,我们将讨论在React中创建Wrapper组件模式,即用于包装其他元素并帮助构建特定布局的组件。这些Wrapper组件不仅可以帮助我们处理空间间距,还可以用于更全面的布局功能,从而轻松实现复杂的样式。 1. Wrapper组件的目的
2. 引入
|
22. Design System Final Project 设计系统最终项目1. Project Assignment 项目任务最终项目:用样式模式构建设置页面在这个最终项目中,我们将使用前面课程中创建的样式模式(patterns)来构建一个实际的网页。我们的目标是通过重用这些模式来提高代码的可维护性和开发效率,同时体验如何利用这些模式简化页面设计。 项目目标创建一个类似用户配置文件或设置页面的网页。页面将包含标题区域、主内容区以及其他可能的内容块。我们会使用以下模式来实现不同区域的布局和样式:
任务说明
实践练习在接下来的课程中,我们将逐步完成这个页面构建过程,你可以在每一步后对照自己的实现与我们的解决方案,看看如何利用这些模式来实现更简洁和灵活的代码。 现在可以开始动手,尝试用课程中的模式来创建页面的各个部分。完成后再继续观看下一个视频,我们将逐步展示如何实现这些部分。 2. Solution Building a Navbar with Menu and Header 解决方案:构建带菜单和标题的导航栏菜单栏和搜索框实现:分步构建设置页面头部在这一部分,我们继续完善前端项目,重点构建页面的头部区域。通过分步讲解,我们实现了菜单栏的样式、搜索框设计,并且准备好下一步要构建的标题部分。 项目进展
下一步在接下来的课程中,我们将集中精力完善页面的其他区域,包括标题部分和左侧导航栏。通过将各部分代码分离并模块化处理,我们最终将构建一个功能完整的设置页面,进一步展示如何高效地使用样式模式来快速实现页面布局和样式。 接下来,尝试在你的项目中复现这些步骤,并将代码与课程内容进行对照,以加深对每个步骤的理解。 3. Solution Building a Sidebar Menu 解决方案:构建侧边栏菜单实现左侧导航栏和侧边栏样式在这个视频中,我们进一步完善了页面的左侧导航栏,为页面布局添加了更多样式和组件。我们通过几个步骤实现了样式设计并且创建了基本布局,为下一步的右侧内容区域打下了基础。 项目进展
下一步下一部分将集中在实现右侧内容区域的布局,进一步完善页面的整体设计。通过继续使用模式化组件和样式,我们将确保整个页面的布局一致性,同时提升代码的可读性和可维护性。 请继续尝试构建该项目,将代码与视频中的步骤对照,以更好地理解每个步骤的实现过程。 4. Solution Building the Form 解决方案:构建表单实现右侧表单内容区域在这部分视频中,我们完成了页面右侧的内容区域,该区域包括了一个个人信息表单和控制按钮。 项目进展
下一步
通过本次视频的构建,我们已经构建了右侧表单区域的整体布局与样式设计。尝试进一步完善这一区域,并调整样式以匹配预期的用户界面设计。 5. Solution Finishing Buttons 解决方案:完成按钮最终步骤:实现表单的 Save 和 Cancel 按钮在这一部分,我们为表单添加了最终的控制元素,即 Save 和 Cancel 按钮。以下是实现细节: 实现过程
总结至此,我们已完整构建了该页面的所有布局和样式。在整个过程中,使用了模块化组件和封装样式的设计理念,保持代码简洁且易于维护。建议进一步实践,通过模仿其他模板来加强这些布局和样式技术的应用。 |
23. Advanced Typescript Introduction 高级Typescript介绍1. Requirements 要求好的,在开始课程之前,我们需要准备什么? 首先,你需要一台可以正常工作的电脑。显然,大家都有这个条件。 但更重要的是,你需要在电脑上安装 Node.js,因为在课程中的一个演示项目里,我们将有一个很小的 server.js 文件,它包含一个由 Node.js 和 Express 编写的后端 API。 别担心,你不需要了解 Node.js 的相关内容,只需要安装好它,这样你就可以运行 server.js 文件。 接下来,我们需要安装 NPM。如果你想使用我在每个讲座中附上的资源,例如运行 NPM install,你需要 NPM,当然,这对于创建自己的新项目也很有用。 至于编辑器,我会选择 VS Code。大家都知道,它是 React 项目中最常用且广泛使用的编辑器。我们将使用 VS Code,因为它具有一些功能,特别是在使用 TypeScript 和 React 应用程序时,非常方便。在后面的课程中,你会看到 VS Code 对 TypeScript 的支持有多好。 此外,提到网络连接是因为在一些讲座中,我们将使用 TypeScript 的一些基本功能,这些功能不一定是 React 组件,而是一些通用的 TypeScript 概念。为此,我会使用 TypeScript playground.org 这样的网站。所以,如果你有网络连接,自己尝试这些内容也会更方便。 如果你具备了以上这些条件,就可以开始了! |
24. Advanced Typescript Typing Hooks 高级Typescript钩子类型1. useState 使用useState使用
|
25. Advanced Typescript Typing Reducers 高级Typescript类型Reducer1. Typing Reducers 类型Reducer使用 TypeScript 和
|
26. Advanced Typescript Typing Context API 高级Typescript Context API类型1. Context API with Types 使用类型的Context API在本视频中,我们探讨了如何使用 React 的 Context API 来简化 主要步骤
使用 Context API 的优势通过 Context API,可以让 |
27. Advanced Typescript Using Generics 高级Typescript使用泛型1. Utility types 实用类型在此视频中,我们探讨了多种 TypeScript 工具函数,这些函数可以简化代码并增强类型安全性,特别是对于 React 应用程序。以下是关键概念的摘要: 关键的 TypeScript 工具函数
这些 TypeScript 工具可以显著帮助您在 TypeScript 项目中控制和精炼类型定义。 2. Generics with Template Literals 带模板文字的泛型大家好,欢迎回来!在这一节课中,我们将讨论 TypeScript 中的泛型。在接下来的几节课中,我们会尝试解决一个在上下文中遇到的问题。之前我们使用了 首先,我决定先来聊聊泛型,并看看我们可以用 TypeScript 的泛型实现哪些强大功能。为此,我们将使用一个在线编辑器。您可以在 Google 搜索 "TypeScript Playground",然后点击 TypeScript 的官网。进去之后,您会看到一个编辑器页面。为了节省时间,我会粘贴一些简单的 TypeScript 代码,并逐步讲解。 接下来,代码中定义了一个名为 在 您可能会觉得这些定义有些复杂,但在大型代码库中,您会经常遇到类似的声明,它们可以节省您很多时间。例如,当您在代码中悬停查看类型信息时,可以看到这些类型是动态设置的,并且不需要多余的代码。 在示例中,我们通过泛型来定义 这就是为什么我们要学习泛型,它在代码复用方面非常有帮助。当我们为 3. More on Generics 更多泛型内容大家好,欢迎回来!在我们开始使用 React 之前,今天我们先深入探讨一下泛型,并通过一些例子来看看它们能带来的具体好处。 首先,大家可能都熟悉一种叫做链表的数据结构。链表包含一个元素,同时还包含指向下一个节点的引用。接下来,我们将创建一个类型来表示链表。让我们称之为 type Linked<T> = {
value: T;
next?: Linked<T>;
}; 这意味着,如果我们创建一个链表类型的实例,实例的 我们可以创建一个具体的链表实例。例如,如果我们想要一个 let textLinked: Linked<string> = {
value: "Hello",
next: { value: "World" }
}; 如果我们给 为了进一步展示 TypeScript 自动处理类型的强大功能,我们可以创建一个函数,帮助我们构建链表。这次,我们用泛型来处理不同的类型,让同一个函数可以生成任何类型的链表。 function buildLink<T>(value: T): Linked<T> {
return { value };
} 通过这个泛型函数,我们可以传递不同的类型,例如 let stringLinkedList = buildLink("Hello");
let numberLinkedList = buildLink(123); 在以上代码中,TypeScript 自动推断出了 因此,TypeScript 泛型不仅提升了代码的灵活性,还大大减少了冗余代码的量,使代码更加干净、易于维护。记住:当你发现代码中有大量重复时,考虑使用泛型来帮助简化代码并提升可读性。 4. Building a Context with Generics 使用泛型构建Context大家好,欢迎回来!在本视频中,我们将使用泛型来修复一个问题。如果你还记得我们之前在颜色演示项目中创建的上下文时遇到的问题,具体来说,在我们的颜色上下文中,我们需要提供两个值:一个是颜色值(hex color,字符串类型),另一个是 问题在于,当我们使用 当时,我们使用了 为了找到更好的解决方案,我们决定使用泛型来确保类型安全。首先,我们在上下文目录下创建了一个新文件,命名为 接下来,我们使用 例如: function createContext<T extends {}>() {
const context = React.createContext<T | undefined>(undefined);
function useContext() {
const colorContext = React.useContext(context);
if (!colorContext) throw new Error("使用上下文时必须提供值。");
return colorContext;
}
return [useContext, context.Provider] as const;
} 在这个 这个泛型 使用泛型使代码更加健壮,并且在创建上下文时大大提高了类型安全性,避免了潜在的调试问题。这种方式使得代码更具可维护性,同时确保了项目的一致性。 5. Consuming a Custom Context 使用自定义Context好了,接下来我们来应用这个解决方案。首先回到 首先,我们不再需要 现在,当我们查看 接着,我们在 接下来,我们还要检查在其他文件中是否使用了 这正是为什么创建自己的钩子是有好处的。因为这样一来,其他组件就不需要关心上下文是如何创建的,也不需要关心默认值的设置,只需要简单地使用这些钩子函数。这样即使以后我们决定从上下文 API 切换到 Redux,也不需要更改整个应用程序中的代码,只需要修改自定义的 这个例子展示了如何通过封装来简化应用程序的逻辑维护。这样我们不需要直接从 React 中导入 最后,我们在浏览器中测试了新逻辑,发现点击颜色按钮后颜色能够成功改变,证明新的 这展示了如何创建自定义 为了进一步实现封装,可以为不同的状态值创建单独的自定义钩子。例如,我们可以创建一个 6. Building a Type Helper 构建类型助手大家好。我们来看看这里的内容吧。主要思想是关于这个字符串或这种灵活的语法。基本上我们在这里使用它,以便在使用此类型(灵活菜单)时可以有自动补全功能,同时还可以输入其他值。 例如,如果你在这里查看,你会看到我们有自动补全功能,也可以输入任何其他字符串。这正是我们在这里使用它的主要原因。这里我们也有相同的功能,如果移除它,你会看到我们有主要和次要的自动补全选项,一切都运作良好。我相信你知道这里的工作原理。 不过,还有改进的空间。正如你所看到的,这个语句或语法被重复使用,因此并不十分简洁。而且对于一些新手来说,可能会觉得难以理解。所以我们希望使其更具描述性、更清晰并更容易理解。 你的任务是创建一个 TypeScript 助手函数。如果你对 TypeScript 助手函数不了解,可以去阅读文档,比如 TypeScript 的文档,并了解什么是类型函数。 你要创建的类型助手的主要责任是包含这个语法,从而使我们能够提取它,并在后续使用时只需在一个地方进行更改。我们可以称之为 现在,让我们看看如何使用这个助手函数。假设我们有一个 我们要做的是,让这个助手函数返回包含这种表达式的组合。让我们用这个助手函数来包装 通过这种方式,你可以捕捉到代码中重复的部分,并可以在单一位置添加注释和说明,使代码更清晰、更易于维护。 7. Another Type Helper 另一个类型助手大家好,欢迎回来。 今天我们将再次回顾一个之前的练习。我相信你们还记得这个。我们有一个输入框组件,这个输入框的 在这里,你必须传递 正如你们看到的,我们需要加上这些括号,还需要在每次需要这种 将这个类型作为泛型参数传递,稍微调整它,并将这个部分添加进去。你可以稍微思考一下,如果有问题,可以回来找我,我们可以一起做。 好了,欢迎回来。接下来,我们将一起完成它。我们将创建一个叫做 你可以重命名,以免混淆。我们将传递一个泛型类型 我们可以创建另一个类型助手,叫做 对于键,我们知道这里的值叫做 让我们创建一个示例类型,使其更清晰。我们将传递 接下来,我们要用 代替之前的代码,我们只需编写 最后,不要让这些名字混淆了你。可以随意更改,例如将它们命名为 8. Generic Constrains 泛型约束好的,我们仍在同一个示例中,继续使用这个漂亮的输入框组件。通过类型助手,我们已经将一个相对复杂的类型简化成更简单的语法,但这里存在一个小问题。 假设我想创建一个类型 你的任务是找到一种方法来约束这个 好了,欢迎回来。接下来让我给你展示一些内容。假设我们有一个函数,暂时命名为 在 TypeScript 中,约束类型的关键字是
接下来,我们在泛型中使用相同的约束,使代码正常工作。如果你创建一个类型,例如 9. Typing a Hook with Generics 使用泛型类型钩子好的,欢迎回来。 在这个有趣的练习中,我们将实现一个类似于自定义 Hook 的
在使用
你可以先暂停视频,试着解决这些问题,再回来查看。 首先,我们要在 接下来,我们要利用这个
我们将返回类型设置为 此外,我们还需要确保 10. Inferring Generic Types 推断泛型类型好的,我们在这里使用 我们传递了一个字符串 解决这个问题其实很简单。首先,我们需要在 虽然我们没有显式传递 例如,如果你在这里传递 11. Generic Components 泛型组件我们在本视频中使用的组件名为
这是一个常见的模式,尤其是当你希望对不同的行应用不同样式时,使用这种模式很方便。在示例中,我们有一组虚拟产品,每个产品只包含 但是,悬停在 首先,我们将 在 更新后,如果你悬停在 这种方法展示了如何创建一个泛型组件。当两个或多个 12. Passing Types to Components 传递类型到组件我们再次回到之前的泛型函数组件,这次要看看如何在 在这里,我们传递了一个随机内容的数组,它会报错,因为没有找到 你的任务是将 欢迎回来!要实现这一点,你可以像在泛型函数中传递参数类型一样为泛型组件传递类型参数。例如,对于 <ProductList<Product> /> 这样,TypeScript 就会检测到传入的类型是否与 13. Reconsidering Generics 重新考虑泛型在本视频中,我们将探讨如何简化一个带有泛型的组件
这是通过泛型的方式实现的,TypeScript 会根据传入的 你的任务是:找到一种更简单的方法来实现同样的功能。虽然泛型在很多情况下有效,但我们是否可以通过更简单的方式,比如联合类型,来解决这个问题? 欢迎回来!我们可以通过使用联合类型来简化代码。 步骤如下:
最终代码结构如下: interface PopupProps {
isOpen: boolean;
variant: "withControls" | "noControls";
}
interface WithControlsProps extends PopupProps {
variant: "withControls";
label: string;
onClick: () => void;
}
interface NoControlsProps extends PopupProps {
variant: "noControls";
}
type Props = WithControlsProps | NoControlsProps; 通过这种方式,我们不再需要复杂的泛型,直接使用联合类型来约束传递的属性。 这样,当你选择 结论:虽然泛型非常强大,但在某些情况下,联合类型提供了更简单的解决方案。在这个例子中,使用联合类型不仅简化了代码,还提升了可读性。选择尽量简单的方案,往往是更好的做法。 |
28. Advanced Typescript More on Typescript 高级Typescript更多内容1. Types vs interfaces 类型 vs 接口在 TypeScript 中, 接口(Interfaces)接口通常用于定义对象和类的结构,帮助指定对象应具备的属性和方法。它们特别适合用于定义对象的“合同”或“蓝图”,使不同的对象共享相同的属性和方法。此外,接口是可扩展的,你可以在需要时为接口添加更多的属性或方法,这使得它们更加灵活。 类型(Types)类型同样用于定义数据结构,但不局限于对象和类。类型可以用于定义函数的类型,或为复杂的类型创建别名,这在提升可读性和复用性方面很有用。与接口不同,类型在定义后不可重新打开或扩展,无法在后续添加新的属性或方法。 选择何种方式?如果你在定义一个公共 API,并希望其他人能够扩展它,接口是不错的选择。接口允许 API 的使用者根据需要添加更多属性或方法。 在 React 组件中,尤其是用于定义 2. Function overloads 函数重载在这段视频中,我们讨论了 TypeScript 的函数重载。函数重载允许你为一个函数定义多个类型签名,从而根据不同的参数组合实现不同的行为。 函数重载的基本示例以
实现步骤
代码示例function add(a: number, b: number): number;
function add(a: number): (b: number) => number;
function add(a: number, b?: number) {
if (b === undefined) {
return (b: number) => a + b;
}
return a + b;
}
// 测试代码
console.log(add(5)(4)); // 输出 9
console.log(add(3, 3)); // 输出 6 测试运行
这种实现方式展示了函数重载如何提供灵活的函数调用方式,使代码更具可读性和扩展性。TypeScript 中的函数重载特别适用于不同参数组合的多态实现,可以帮助开发者在编写复杂应用时更有效地管理函数行为。 |
29. Advanced Typescript Component Patterns 高级Typescript组件模式1. Higher Order components Part1 高阶组件 Part1在这一部分视频中,我们讨论了 React 中的组件模式,特别是高阶组件(HOC)的使用。我们通过一个简单的示例项目来展示如何将逻辑从组件中分离出来,创建一个“展示组件” (presentational component) 和一个负责逻辑的高阶组件。下面是具体步骤和示例。 1. 现有代码概述
2. 任务说明目标: 将鼠标位置检测逻辑从 3. 解决方案步骤:
// DisplayMousePosition.tsx
import React from 'react';
interface MouseProps {
x: number;
y: number;
}
const DisplayMousePosition: React.FC<MouseProps> = ({ x, y }) => (
<div>
X: {x}, Y: {y}
</div>
);
export default DisplayMousePosition; // withMouseMove.tsx
import React, { useState, useEffect } from 'react';
interface Position {
x: number;
y: number;
}
const withMouseMove = <P extends Position>(Component: React.ComponentType<P>) => {
return (props: Omit<P, keyof Position>) => {
const [position, setPosition] = useState<Position>({ x: 0, y: 0 });
const handleMouseMove = (event: MouseEvent) => {
setPosition({
x: event.clientX,
y: event.clientY,
});
};
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return <Component {...(props as P)} x={position.x} y={position.y} />;
};
};
export default withMouseMove; // App.tsx
import React from 'react';
import DisplayMousePosition from './DisplayMousePosition';
import withMouseMove from './withMouseMove';
const EnhancedMouseComponent = withMouseMove(DisplayMousePosition);
const App = () => (
<div>
<h1>Mouse Position Tracker</h1>
<EnhancedMouseComponent />
</div>
);
export default App; 4. 代码解析
通过这种模式,高阶组件将位置逻辑与 2. Higher Order components Part2 高阶组件 Part2首先,我们要在组件文件夹中创建一个名为
在 对于展示部分,我们使用 TypeScript 定义类型。首先定义一个 然后我们创建 接下来,我们定义一个辅助函数 最后,返回被包装的组件,并传递 X 和 Y 以及 在浏览器中进行测试时,可以看到组件已经正常工作。使用高阶组件的好处在于它增强了组件的可测试性和可复用性。通过将逻辑拆分为独立的部分,组件更易于进行单元测试。 3. Render Props 渲染属性欢迎回来!我们今天要使用一种名为 render props 的模式来解决与鼠标位置相关的问题。虽然这种模式比高阶组件(HOC)稍旧,但它在某些场景下依然非常强大。 什么是 Render Props?Render Props 的核心思想是创建一个包装组件,该组件可以进行一些计算或从 API 获取数据,然后将结果作为 props 传递给它的子组件。这种方式简单直接。 为了实现它,我们首先在组件文件夹中新建一个目录,命名为 首先,我们将其定义为一个常量,命名为 接着,我们定义一个状态 为了处理鼠标移动事件,我们创建一个 最后,我们在 使用 Render Props要在应用中使用这个组件,我们可以在 为了保持代码整洁,我们可以将 总结Render Props 模式的主要优点在于它可以很好地将逻辑和展示分离,使代码更加模块化和可复用。通过这种模式,我们不仅能更灵活地控制组件内部的逻辑,也更方便地对其进行测试和扩展。希望这次的讲解让你了解了 render props 的应用场景和优势。 4. Custom Hooks 自定义钩子亲爱的同学们,大家好!欢迎回来。 在本视频中,我们将优化鼠标位置的示例,使其更加简洁、易用,并提高复用性和测试性。这次我们会使用另一种常见模式:自定义 Hook(Custom Hook)。相信大家都熟悉它,并知道如何使用,不过我们今天会用 TypeScript 来实现它,看看是否能学到一些 TypeScript 的知识。 首先,我们创建一个名为 在 接下来,我们定义一个 然后,我们返回一个包含 X、Y 和 在应用中使用自定义 Hook我们可以在 接着,我们可以将这些属性传递给任意组件,如一个展示鼠标位置的组件。这种方式使得代码更加简洁和清晰,也有助于提高组件的复用性和测试性。 在浏览器中验证效果后,您就可以看到自定义 Hook 的实际效果。 总结现在你已经掌握了如何使用 React 的一些主要组件模式,并通过 TypeScript 进一步增强了代码的可读性和类型安全性。如果有任何问题,欢迎随时在问答区提问。 5. Limiting Prop Composition 限制属性组合大家好,欢迎回来! 在本视频中,我们将介绍一种在 React 和 TypeScript 中实现组件功能的模式。比如,在这个小项目中,我们有两个按钮:一个是主按钮(Primary Button),另一个是次按钮(Secondary Button)。当然,你也可以有其他按钮类型,但为了简单起见,我们这里只用这两个按钮。 目前我们有一个简单的按钮组件,你可以传递子元素,比如这里我们传入的是字符串。我们也可以通过 为此,我们可以在 使用 Build Class Names 函数我们在 然后在 限制 Props 组合我们创建两个类型: 在应用中使用在使用组件时,你可以指定按钮类型(如 总结这种方法可以让你通过 TypeScript 限制组件 6. Requiring Prop Composition 需要属性组合大家好,欢迎回来! 今天我们将讨论如何在组件中要求传入的 props 进行组合。首先来看这个简单的演示项目。我们有一个简单的文本组件,可以称之为 如果 在 TextBand 组件
使用 TypeScript 进行函数重载为了实现这种功能,我们将使用函数重载。我们为
我们使用 TypeScript 的重载功能来创建这两个不同版本的 使用示例现在我们可以测试这个功能。在 这种方式不仅在项目规模变大时非常实用,而且在多人合作中也能减少错误,提高代码的可维护性。 额外的 Props 支持另外,如果我们希望将额外的原生 希望你们喜欢这个功能扩展的介绍,并能在项目中加以应用! |
30. Bonus 额外1. Render Props 渲染属性好的,接下来我们要讨论的模式是渲染 渲染 首先,我们有一个 创建 ListHandler 组件
代码如下: const ListHandler = ({ items, keyExtractor, renderItem }) => {
return (
<div>
{items.map((item, index) => (
<div key={keyExtractor(item)}>
{renderItem(item, index)}
</div>
))}
</div>
);
}; 我们将导出 export default ListHandler; 创建 DisplayBooks 组件接下来,我们创建一个名为 在 import styled from 'styled-components';
const Container = styled.div`
padding: 20px;
`;
const BookTitle = styled.h3`
font-size: 1.5rem;
margin: 10px 0;
`; 在 const DisplayBooks = () => {
const booksData = books.slice(0, 5); // 只获取前五项
return (
<Container>
<BookTitle>Book List</BookTitle>
<ListHandler
items={booksData}
keyExtractor={book => book.id}
renderItem={(item) => (
<div>{item.title}</div>
)}
/>
</Container>
);
};
export default DisplayBooks; 总结通过渲染 希望这个示例能够帮助你理解渲染 2. Wrapper Component 包装组件欢迎回来,接下来我们要讨论的模式称为“包装器组件” (Wrapper Component)。 包装器组件的作用正如其名,是一种包装其他组件并向其传递 示例:创建一个 DatePicker 包装器组件假设你想在应用的多个地方使用这个日期选择器,并且在其上方添加一个标签 (label),例如 “选择日期” 或 “选择生日”。为此,我们可以创建一个 DatePicker 包装器组件。 首先,在 import React from 'react';
import DatePicker from 'react-datepicker';
import 'react-datepicker/dist/react-datepicker.css';
const CustomDatePicker = ({ label, ...props }) => {
return (
<div>
{label && <label>{label}</label>}
<DatePicker {...props} />
</div>
);
};
export default CustomDatePicker; 在这个组件中, 使用 CustomDatePicker 组件现在,我们可以在另一个组件中使用 import React, { useState } from 'react';
import CustomDatePicker from './CustomDatePicker';
const WrapperComponent = () => {
const [date, setDate] = useState(null);
return (
<CustomDatePicker
label="选择生日"
selected={date}
onChange={setDate}
/>
);
};
export default WrapperComponent; 在这个组件中,我们通过 总结通过使用包装器组件模式,可以轻松地在第三方组件上添加额外的功能和样式。这样可以提高组件的可重用性、扩展性和可替换性。如果需要更换日期选择器库,只需在包装器组件中替换实现,而不影响其他使用了 希望这个示例能帮助你理解包装器组件的使用场景和优势! 3. Polymorphic Component 多态组件大家好,欢迎收看本视频。本视频将讨论“多态化的 React 组件”。 什么是多态化组件?多态化组件模式为组件使用者提供灵活性,允许他们指定子组件渲染的元素类型。例如,假设你有一个按钮组件,具备不同的样式和变体。有时可能希望能够渲染其他元素,比如链接,而不是按钮。 示例:创建多态化按钮组件假设我们有一个按钮组件,但希望根据需要渲染成 实现步骤
import React from 'react';
const PolymorphicButton = ({ as: Component = 'button', children, ...props }) => {
return (
<Component {...props}>
{children}
</Component>
);
};
export default PolymorphicButton; 在这个例子中,我们使用
import React from 'react';
import PolymorphicButton from './PolymorphicButton';
const App = () => {
return (
<div>
{/* 使用默认按钮 */}
<PolymorphicButton>普通按钮</PolymorphicButton>
{/* 使用 <a> 标签样式 */}
<PolymorphicButton as="a" href="https://example.com">链接按钮</PolymorphicButton>
</div>
);
};
export default App; 在这个示例中,我们创建了两个按钮实例,一个默认渲染为 总结多态化组件是一个强大的设计模式,它允许使用者根据需求在同一个组件上使用不同的 HTML 元素,同时保留其样式和行为。这个模式不仅可以应用于 HTML 基本元素,还可以与自定义组件一起使用,使代码更具灵活性和可重用性。尝试在项目中使用这个模式吧! |
31. Appendix A - Typescript Basics 附录 A - Typescript基础1. Typescript via Intellisense 通过Intellisense使用Typescript以下是翻译和简化后的内容: 大家好,欢迎回来。今天我们开始介绍 TypeScript。 TypeScript 简介在我们的示例代码中, 如果你的答案是 TypeScript,那你答对了。因为这个文件扩展名为 TypeScript 自动推断类型TypeScript 强大的地方在于它会自动推断代码中的类型。除非它无法识别类型,否则一般情况下你不需要手动指定类型。比如,如果你在 VS Code 中悬停在 让我们看看一个简单的示例: function testFunction() {
return 5 - 3;
} 在这个函数中,TypeScript 能自动识别 结论TypeScript 是一个强大的工具,可以帮助我们自动推断类型。它通常可以准确地理解代码的类型信息,只有在它无法自动识别时,才需要开发者手动指定类型。这使得 TypeScript 成为一个非常实用的伙伴。 2. Defining Type of Props 定义属性类型大家好,欢迎回来!今天我们来谈谈如何在 TypeScript 中为函数组件的 props 指定类型。 设置 props 类型首先,在 要解决这个问题,我们需要在组件的定义中明确指出 function ClassInfo({ name }: { name: string }) {
return <div>{name}</div>;
} 通过这种方式,我们明确了 定义更复杂的 props 类型如果组件有多个 props,可以将这些类型集中定义在一个类型接口中,例如: type ClassInfoProps = {
name: string;
course: string;
};
function ClassInfo({ name, course }: ClassInfoProps) {
return (
<div>
<p>Instructor: {name}</p>
<p>Course: {course}</p>
</div>
);
} 通过定义一个 总结在 TypeScript 中,使用类型接口来定义 props 是一种良好的实践,它不仅可以避免类型错误,还能让代码结构更简洁易读。 3. Migrating From JS to TS Exercise 从JS迁移到TS练习大家好,今天我们来实践如何将 JavaScript 组件转换为 TypeScript 组件,并且为其添加类型。 练习步骤我们将进行以下几个步骤:
步骤 1:更改文件扩展名首先,重命名文件,将 步骤 2:定义 props 类型在 TypeScript 中为组件定义类型的一种标准方法是通过 type DashboardProps = {
inputName: string;
handleChange: React.ChangeEventHandler<HTMLInputElement>;
}; 步骤 3:为
|
32. ---LEGACY--- Performance Optimization 旧版- 性能优化1. The demo project 演示项目2. Getting up and running with the demo codes 使用示例代码启动和运行3. Introduction to the React Profiler React Profiler介绍4. Introduction to React Rendering React渲染介绍5. The Virtual DOM 虚拟DOM6. Preventing Wasted Renders in a Simple Component 在简单组件中防止浪费渲染7. Preventing Wasted Renders in Functional Components 在函数组件中防止浪费渲染8. Preventing Wasted Renders When Dealing With Complex Props 处理复杂属性时防止浪费渲染9. Using Immutable Data in Order to Allow for Comparisons 使用不可变数据进行比较10. Preventing Wasted Renders in Repeated Components 在重复组件中防止浪费渲染11. Resources 资源12. Catching Expensive Operations 捕捉昂贵操作13. Reducing Bundle Sizes 减小包大小14. Lazy Loading Components 懒加载组件15. Resources 资源 |
React进阶开发 设计系统, 设计模式, 性能优化Advanced React Design System, Design Patterns, Performance
初步完成进度:22。
调试完成段落:9。
目录
1. 介绍
2. 设计模式布局组件
3. 设计模式容器组件
4. 设计模式受控和非受控组件
5. 设计模式高阶组件
6. 设计模式自定义钩子
7. React中的函数式编程设计模式
8. 更多设计模式
9. 高级概念和钩子
10. 代码清理技巧
11. 可扩展项目架构
12. API层和异步操作
13. 使用React-Query的API层
14. 状态管理模式
15. 性能优化
16. 设计系统核心概念
17. 使用Figma构建组件的设计系统
18. 在React中开发组件的设计系统
19. 封装样式的设计系统
20. 间距模式的设计系统
21. 更复杂样式的设计系统模式
22. 设计系统最终项目
23. 高级Typescript介绍
24. 高级Typescript钩子类型
25. 高级Typescript类型Reducer
26. 高级Typescript Context API类型
27. 高级Typescript使用泛型
28. 高级Typescript更多内容
29. 高级Typescript组件模式
30. 额外
31. 附录 A - Typescript基础
32. 旧版- 性能优化
The text was updated successfully, but these errors were encountered: