乐闻世界logo
搜索文章和话题

服务端面试题手册

SolidJS 如何进行单元测试和集成测试?有哪些测试工具推荐?

SolidJS 提供了强大的测试工具和方法来测试组件和响应式逻辑:测试工具:import { render, screen, fireEvent, waitFor } from '@solidjs/testing-library';import { describe, it, expect } from 'vitest';describe('Counter Component', () => { it('renders initial count', () => { render(() => <Counter />); expect(screen.getByText('Count: 0')).toBeInTheDocument(); }); it('increments count when button clicked', async () => { render(() => <Counter />); const button = screen.getByText('+'); fireEvent.click(button); await waitFor(() => { expect(screen.getByText('Count: 1')).toBeInTheDocument(); }); });});测试响应式逻辑:import { createSignal, createEffect } from 'solid-js';import { createRoot } from 'solid-js';describe('Reactive Logic', () => { it('tracks signal changes', () => { createRoot((dispose) => { const [count, setCount] = createSignal(0); let effectCallCount = 0; createEffect(() => { effectCallCount++; count(); }); expect(effectCallCount).toBe(1); setCount(1); expect(effectCallCount).toBe(2); dispose(); }); });});测试异步操作:import { createResource } from 'solid-js';describe('Async Operations', () => { it('handles loading state', async () => { const fetchMock = vi.fn(() => new Promise(resolve => setTimeout(() => resolve({ data: 'test' }), 100)) ); const [data] = createResource(fetchMock); expect(data.loading).toBe(true); await waitFor(() => expect(data.loading).toBe(false)); expect(data()).toEqual({ data: 'test' }); });});测试用户交互:describe('User Interactions', () => { it('handles form submission', () => { render(() => <LoginForm />); const input = screen.getByLabelText('Email'); const button = screen.getByText('Submit'); fireEvent.input(input, { target: { value: 'test@example.com' } }); fireEvent.click(button); expect(screen.getByText('Submitted: test@example.com')).toBeInTheDocument(); });});测试路由:import { Router } from '@solidjs/router';describe('Routing', () => { it('renders correct component for route', () => { render(() => ( <Router> <Routes> <Route path="/" component={Home} /> <Route path="/about" component={About} /> </Routes> </Router> )); expect(screen.getByText('Home Page')).toBeInTheDocument(); });});最佳实践:使用 @solidjs/testing-library 进行组件测试使用 createRoot 包装测试以自动清理测试用户行为而非实现细节使用 waitFor 处理异步操作Mock 外部依赖和 API 调用保持测试独立和可重复
阅读 0·2月21日 15:23

SolidJS 如何实现服务端渲染(SSR)?有哪些渲染模式?

SolidJS 支持服务端渲染(SSR),提供多种渲染模式:基本 SSR 设置:// entry-server.jsximport { renderToString } from 'solid-js/web';import App from './App';export function render(url) { return renderToString(() => <App url={url} />);}// entry-client.jsximport { hydrate } from 'solid-js/web';import App from './App';hydrate(() => <App />, document);渲染模式:静态生成(SSG):import { renderToStringAsync } from 'solid-js/web';export async function getStaticPaths() { return ['/about', '/contact'];}export default async function Page({ params }) { return renderToStringAsync(() => <Component />);}服务端渲染(SSR):import { StartServer, createHandler } from '@solidjs/start/server';export default createHandler(() => ( <StartServer document={({ assets, children }) => ( <html> <head>{assets}</head> <body>{children}</body> </html> )} />));流式渲染:import { renderToStream } from 'solid-js/web';app.get('*', async (req, res) => { const stream = renderToStream(() => <App />); stream.pipe(res);});数据获取:// 使用 createResource 处理异步数据const [data] = createResource(fetchData, { initialValue: null, deferStream: true // 支持流式传输});// 服务端数据预取export async function getData() { return await fetchData();}同构应用:// 在服务端和客户端都能运行的代码function Component() { const isServer = useIsServer(); return ( <div> {isServer() ? 'Server' : 'Client'} </div> );}性能优化:使用 deferStream 延迟加载预取关键数据优化 hydration 过程使用 Suspense 边界
阅读 0·2月21日 15:22

Astro 中的状态管理是如何实现的?如何在 React、Vue、Svelte 组件中管理状态?

Astro 的状态管理对于构建交互式应用非常重要。虽然 Astro 默认是静态的,但可以通过多种方式实现状态管理。客户端状态管理:React 集成: // src/components/Counter.jsx import { useState, useEffect } from 'react'; export function Counter() { const [count, setCount] = useState(0); useEffect(() => { const saved = localStorage.getItem('count'); if (saved) setCount(parseInt(saved)); }, []); useEffect(() => { localStorage.setItem('count', count.toString()); }, [count]); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(c => c + 1)}>Increment</button> <button onClick={() => setCount(c => c - 1)}>Decrement</button> </div> ); }Vue 集成: <!-- src/components/TodoList.vue --> <script setup> import { ref, computed } from 'vue'; const todos = ref([ { id: 1, text: 'Learn Astro', completed: false }, { id: 2, text: 'Build app', completed: true }, ]); const completedCount = computed(() => todos.value.filter(t => t.completed).length ); function addTodo(text) { todos.value.push({ id: Date.now(), text, completed: false, }); } function toggleTodo(id) { const todo = todos.value.find(t => t.id === id); if (todo) todo.completed = !todo.completed; } </script> <template> <div> <p>Completed: {{ completedCount }} / {{ todos.length }}</p> <ul> <li v-for="todo in todos" :key="todo.id"> <input type="checkbox" :checked="todo.completed" @change="toggleTodo(todo.id)" /> <span :style="{ textDecoration: todo.completed ? 'line-through' : 'none' }"> {{ todo.text }} </span> </li> </ul> </div> </template>Svelte 集成: <!-- src/components/ShoppingCart.svelte --> <script> import { writable } from 'svelte/store'; const cart = writable([]); const total = writable(0); function addToCart(product) { cart.update(items => [...items, product]); total.update(t => t + product.price); } function removeFromCart(index) { cart.update(items => { const removed = items[index]; total.update(t => t - removed.price); return items.filter((_, i) => i !== index); }); } </script> <div> <h2>Shopping Cart</h2> <p>Total: ${$total}</p> <ul> {#each $cart as item, index} <li> {item.name} - ${item.price} <button on:click={() => removeFromCart(index)}>Remove</button> </li> {/each} </ul> </div>全局状态管理(Zustand):// src/store/useStore.tsimport { create } from 'zustand';interface StoreState { user: User | null; theme: 'light' | 'dark'; setUser: (user: User) => void; setTheme: (theme: 'light' | 'dark') => void;}export const useStore = create<StoreState>((set) => ({ user: null, theme: 'light', setUser: (user) => set({ user }), setTheme: (theme) => set({ theme }),}));// src/components/Header.jsximport { useStore } from '../store/useStore';export function Header() { const { user, theme, setTheme } = useStore(); return ( <header> <h1>My App</h1> {user ? <p>Welcome, {user.name}</p> : <p>Please login</p>} <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}> {theme === 'light' ? '🌙' : '☀️'} </button> </header> );}服务端状态管理(React Query):// src/lib/api.tsexport async function fetchPosts() { const response = await fetch('/api/posts'); return response.json();}export async function fetchPost(id: string) { const response = await fetch(`/api/posts/${id}`); return response.json();}// src/components/PostList.jsximport { useQuery } from '@tanstack/react-query';import { fetchPosts } from '../lib/api';export function PostList() { const { data, isLoading, error } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts, }); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <ul> {data.map(post => ( <li key={post.id}> <a href={`/blog/${post.slug}`}>{post.title}</a> </li> ))} </ul> );}表单状态管理:// src/components/ContactForm.jsximport { useState } from 'react';export function ContactForm() { const [formData, setFormData] = useState({ name: '', email: '', message: '', }); const [errors, setErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); function handleChange(e) { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); } async function handleSubmit(e) { e.preventDefault(); setIsSubmitting(true); try { const response = await fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData), }); if (!response.ok) throw new Error('Submission failed'); alert('Message sent!'); setFormData({ name: '', email: '', message: '' }); } catch (error) { setErrors({ submit: error.message }); } finally { setIsSubmitting(false); } } return ( <form onSubmit={handleSubmit}> <input name="name" value={formData.name} onChange={handleChange} placeholder="Name" required /> <input name="email" type="email" value={formData.email} onChange={handleChange} placeholder="Email" required /> <textarea name="message" value={formData.message} onChange={handleChange} placeholder="Message" required /> <button type="submit" disabled={isSubmitting}> {isSubmitting ? 'Sending...' : 'Send'} </button> {errors.submit && <p className="error">{errors.submit}</p>} </form> );}URL 状态管理:// src/components/ProductFilter.jsximport { useSearchParams } from 'react-router-dom';export function ProductFilter() { const [searchParams, setSearchParams] = useSearchParams(); const category = searchParams.get('category') || 'all'; const sort = searchParams.get('sort') || 'name'; const page = parseInt(searchParams.get('page') || '1'); function updateFilter(key, value) { setSearchParams(prev => { const newParams = new URLSearchParams(prev); newParams.set(key, value); return newParams; }); } return ( <div> <select value={category} onChange={(e) => updateFilter('category', e.target.value)} > <option value="all">All Categories</option> <option value="electronics">Electronics</option> <option value="clothing">Clothing</option> </select> <select value={sort} onChange={(e) => updateFilter('sort', e.target.value)} > <option value="name">Name</option> <option value="price">Price</option> <option value="date">Date</option> </select> <div> <button disabled={page === 1} onClick={() => updateFilter('page', page - 1)} > Previous </button> <span>Page {page}</span> <button onClick={() => updateFilter('page', page + 1)}> Next </button> </div> </div> );}服务端状态(SSR):---// src/pages/dashboard.astroimport { getEntry } from 'astro:content';// 服务端获取数据const user = await getUser(Astro.request);const posts = await getCollection('blog');const stats = await fetchStats(user.id);---<div class="dashboard"> <h1>Welcome, {user.name}</h1> <div class="stats"> <div class="stat"> <h3>Total Posts</h3> <p>{stats.totalPosts}</p> </div> <div class="stat"> <h3>Total Views</h3> <p>{stats.totalViews}</p> </div> </div> <div class="posts"> {posts.map(post => ( <article> <h2>{post.data.title}</h2> <p>{post.data.description}</p> </article> ))} </div></div>最佳实践:选择合适的状态管理方案:简单状态:使用框架内置状态(useState、ref)复杂状态:使用状态管理库(Zustand、Redux)服务端状态:使用数据获取库(React Query)保持状态简洁:只存储必要的数据避免冗余状态使用派生状态性能优化:使用 memo 避免不必要的重渲染实现虚拟滚动处理大量数据使用防抖和节流优化频繁更新类型安全:使用 TypeScript 定义状态类型确保状态更新类型正确使用类型推导减少错误测试状态管理:编写单元测试验证状态逻辑测试状态更新函数模拟异步状态更新Astro 的状态管理方案灵活多样,可以根据项目需求选择最适合的方案。
阅读 0·2月21日 15:22

Expo应用中如何进行状态管理?有哪些推荐方案?

Expo应用的状态管理是一个重要的架构决策,需要根据项目规模、团队经验和性能要求选择合适的方案。Expo本身不提供特定的状态管理库,但支持所有React Native的状态管理解决方案。主流状态管理方案:React Context + Hooks适用于中小型应用,简单直接。实现示例:// Context创建const AppContext = createContext();// Provider组件export function AppProvider({ children }) { const [state, setState] = useState(initialState); return ( <AppContext.Provider value={{ state, setState }}> {children} </AppContext.Provider> );}// 使用Contextfunction MyComponent() { const { state, setState } = useContext(AppContext); return <Text>{state.value}</Text>;}优点:无需额外依赖简单易用性能良好(配合useMemo和useCallback)缺点:大型应用可能导致性能问题缺少中间件支持调试工具有限Redux Toolkit适用于中大型应用,提供完整的生态系统。安装和配置:npm install @reduxjs/toolkit react-redux实现示例:// 创建sliceimport { createSlice } from '@reduxjs/toolkit';const counterSlice = createSlice({ name: 'counter', initialState: { value: 0 }, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, },});export const { increment, decrement } = counterSlice.actions;export default counterSlice.reducer;// 配置storeimport { configureStore } from '@reduxjs/toolkit';const store = configureStore({ reducer: { counter: counterReducer, },});// 在Expo中使用import { Provider } from 'react-redux';function App() { return ( <Provider store={store}> <RootNavigator /> </Provider> );}优点:强大的中间件支持优秀的调试工具(Redux DevTools)时间旅行调试丰富的生态系统缺点:学习曲线较陡样板代码较多可能过度设计小型应用Zustand轻量级状态管理库,API简洁。安装和配置:npm install zustand实现示例:import create from 'zustand';const useStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })),}));// 在组件中使用function Counter() { const { count, increment, decrement } = useStore(); return ( <View> <Text>{count}</Text> <Button title="+" onPress={increment} /> <Button title="-" onPress={decrement} /> </View> );}优点:API简洁无需Provider性能优秀TypeScript支持良好缺点:生态系统较小中间件支持有限Jotai基于原子的状态管理,灵活且高性能。安装和配置:npm install jotai实现示例:import { atom, useAtom } from 'jotai';// 创建atomconst countAtom = atom(0);const doubledAtom = atom((get) => get(countAtom) * 2);// 在组件中使用function Counter() { const [count, setCount] = useAtom(countAtom); const [doubled] = useAtom(doubledAtom); return ( <View> <Text>Count: {count}</Text> <Text>Doubled: {doubled}</Text> <Button title="Increment" onPress={() => setCount(count + 1)} /> </View> );}优点:细粒度更新无需Provider高性能灵活组合缺点:相对较新学习曲线RecoilFacebook开发的实验性状态管理库。安装和配置:npm install recoil实现示例:import { atom, useRecoilState, useRecoilValue } from 'recoil';// 创建atomconst countState = atom({ key: 'countState', default: 0,});// 在组件中使用function Counter() { const [count, setCount] = useRecoilState(countState); return ( <View> <Text>{count}</Text> <Button title="Increment" onPress={() => setCount(count + 1)} /> </View> );}选择建议:小型应用:React Context + Hooks简单直接无需额外依赖快速开发中型应用:Zustand或Jotai轻量级API简洁性能优秀大型应用:Redux Toolkit完整生态系统强大的调试工具团队协作友好需要细粒度控制:Jotai或Recoil原子化状态精确更新高性能最佳实践:按需选择:根据项目规模选择合适的方案保持简单:不要过度设计TypeScript支持:优先选择有良好TypeScript支持的库性能优化:使用useMemo、useCallback等优化渲染持久化:考虑使用AsyncStorage或expo-secure-store持久化状态持久化方案:// 使用expo-secure-storeimport * as SecureStore from 'expo-secure-store';// 保存状态await SecureStore.setItemAsync('userState', JSON.stringify(userState));// 恢复状态const savedState = await SecureStore.getItemAsync('userState');if (savedState) { const userState = JSON.parse(savedState); // 恢复到状态管理库}选择合适的状态管理方案是Expo应用架构的关键决策,需要综合考虑项目需求、团队技能和长期维护成本。
阅读 0·2月21日 15:22

SVG 如何与 CSS 结合使用

SVG 与 CSS 的结合使用可以创建丰富的视觉效果和交互体验。以下是 SVG 与 CSS 结合的多种方式:1. 基本样式应用CSS 可以直接应用于 SVG 元素,就像应用于 HTML 元素一样。<svg width="200" height="200"> <style> .circle { fill: blue; stroke: red; stroke-width: 2; } .rectangle { fill: green; stroke: black; stroke-width: 3; } </style> <circle class="circle" cx="100" cy="100" r="50" /> <rect class="rectangle" x="20" y="20" width="80" height="60" /></svg>2. CSS 伪类使用 CSS 伪类实现交互效果。<svg width="200" height="200"> <style> .interactive { fill: blue; transition: all 0.3s ease; cursor: pointer; } .interactive:hover { fill: red; transform: scale(1.1); } .interactive:active { fill: green; } .interactive:focus { outline: 3px solid #005fcc; outline-offset: 2px; } </style> <circle class="interactive" cx="100" cy="100" r="50" tabindex="0" /></svg>3. CSS 动画使用 CSS 的 @keyframes 实现动画效果。<svg width="200" height="200"> <style> .rotating { transform-origin: center; animation: rotate 2s linear infinite; } .pulsing { animation: pulse 1s ease-in-out infinite; } @keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.2); } } </style> <rect class="rotating" x="75" y="75" width="50" height="50" fill="blue" /> <circle class="pulsing" cx="100" cy="100" r="30" fill="red" /></svg>4. CSS 变量使用 CSS 变量实现动态样式。<svg width="200" height="200"> <style> :root { --primary-color: #3498db; --secondary-color: #e74c3c; --stroke-width: 2px; } .dynamic { fill: var(--primary-color); stroke: var(--secondary-color); stroke-width: var(--stroke-width); } .dynamic:hover { --primary-color: #2ecc71; } </style> <circle class="dynamic" cx="100" cy="100" r="50" /></svg>5. 外部 CSS 文件将 SVG 样式放在外部 CSS 文件中。<!-- HTML 文件 --><link rel="stylesheet" href="styles.css" /><svg width="200" height="200"> <circle class="styled-circle" cx="100" cy="100" r="50" /></svg>/* styles.css */.styled-circle { fill: #3498db; stroke: #2c3e50; stroke-width: 3px; transition: all 0.3s ease;}.styled-circle:hover { fill: #e74c3c; transform: scale(1.1);}6. CSS 选择器使用各种 CSS 选择器精确控制 SVG 元素。<svg width="200" height="200"> <style> /* ID 选择器 */ #unique-circle { fill: red; } /* 类选择器 */ .blue-circle { fill: blue; } /* 属性选择器 */ circle[fill="green"] { stroke: black; stroke-width: 2; } /* 后代选择器 */ .group circle { fill: purple; } /* 伪元素 */ .special::after { content: ''; /* SVG 不支持伪元素,但可以用于 SVG 内的 HTML 元素 */ } </style> <circle id="unique-circle" cx="50" cy="50" r="20" /> <circle class="blue-circle" cx="100" cy="50" r="20" /> <circle fill="green" cx="150" cy="50" r="20" /> <g class="group"> <circle cx="50" cy="100" r="20" /> <circle cx="100" cy="100" r="20" /> </g></svg>7. 响应式 SVG结合 CSS 媒体查询实现响应式 SVG。<svg width="100%" height="auto" viewBox="0 0 200 200"> <style> @media (max-width: 768px) { .responsive-element { fill: blue; } } @media (min-width: 769px) { .responsive-element { fill: red; } } </style> <circle class="responsive-element" cx="100" cy="100" r="50" /></svg>8. CSS transform使用 CSS transform 实现变换效果。<svg width="200" height="200"> <style> .transformed { transition: transform 0.3s ease; } .transformed:hover { transform: translate(20px, 20px) rotate(45deg) scale(1.2); } </style> <rect class="transformed" x="75" y="75" width="50" height="50" fill="blue" /></svg>最佳实践:优先使用 CSS 实现动画和交互将样式与结构分离使用 CSS 变量提高可维护性考虑性能,避免过度使用复杂动画测试跨浏览器兼容性使用 CSS 过渡实现平滑效果
阅读 0·2月21日 15:22

SVG 与其他图形格式有什么区别和优劣

SVG 与其他图形格式的对比是选择合适技术的重要依据。以下是 SVG 与其他常见图形格式的详细对比:1. SVG vs PNG/JPG(位图格式)SVG 优势:矢量图形,无限缩放不失真文件大小通常更小可编辑和动画支持 CSS 和 JavaScript 交互可访问性好,支持屏幕阅读器SEO 友好,内容可被搜索引擎索引PNG/JPG 优势:适合照片和复杂图像浏览器兼容性更好文件格式更简单不需要额外的解析开销适用场景:SVG:图标、logo、图表、插画、需要缩放的图形PNG/JPG:照片、复杂图像、不需要编辑的图形2. SVG vs GIFSVG 优势:动画更流畅(CSS/JS 动画)文件大小更小支持交互颜色深度更高无版权问题GIF 优势:动画兼容性更好支持透明背景文件格式简单社交媒体支持广泛适用场景:SVG:现代网页动画、交互式动画GIF:简单的循环动画、社交媒体分享3. SVG vs CanvasSVG 优势:矢量图形,无限缩放DOM 元素,支持事件和交互可访问性好易于调试和编辑SEO 友好Canvas 优势:渲染大量对象时性能更好适合游戏和复杂动画像素级控制不受 DOM 限制适用场景:SVG:图标、图表、简单动画、需要交互的场景Canvas:游戏、大数据可视化、复杂动画、高性能场景4. SVG vs WebPSVG 优势:矢量图形,可无限缩放可编辑和动画支持交互可访问性好WebP 优势:压缩率更高,文件更小支持动画浏览器支持越来越广泛适合照片和复杂图像适用场景:SVG:矢量图形、图标、图表WebP:照片、复杂图像、需要高压缩率的场景5. SVG vs 图标字体(Icon Fonts)SVG 优势:可以使用多种颜色和渐变支持动画和交互更好的可访问性可以精确控制样式不需要额外的字体文件图标字体优势:实现简单,兼容性好可以像文字一样设置样式文件大小小支持文本效果适用场景:SVG:需要多色图标、动画图标、交互式图标图标字体:单色图标、需要广泛兼容性的场景6. SVG vs PDFSVG 优势:网页原生支持可以嵌入 HTML支持动画和交互文件格式更轻量PDF 优势:打印质量更好跨平台一致性支持复杂的文档结构更适合文档分发适用场景:SVG:网页图形、交互式内容PDF:文档、打印、跨平台分发7. 性能对比文件大小:简单图形:SVG < PNG < JPG复杂图像:JPG < WebP < PNG < SVG图标:SVG ≈ 图标字体 < PNG渲染性能:少量元素:SVG > Canvas大量元素:Canvas > SVG动画:CSS 动画 > SMIL > JS 动画8. 选择建议选择 SVG 的情况:需要矢量缩放需要交互和动画需要可访问性需要编辑和修改SEO 重要选择其他格式的情况:照片或复杂图像:PNG/JPG/WebP高性能渲染大量对象:Canvas简单单色图标:图标字体文档分发:PDF最佳实践:根据需求选择合适的格式可以组合使用多种格式考虑浏览器兼容性优化文件大小和性能测试不同场景的效果
阅读 0·2月21日 15:22