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

面试题手册

Garfish 的生命周期管理包括哪些钩子函数,它们的作用和执行顺序是什么?

Garfish 提供了完整的生命周期管理机制,确保子应用的加载、挂载、更新和卸载过程可控且可预测。生命周期钩子1. bootstrap(初始化)触发时机:子应用首次加载时作用:执行子应用的初始化逻辑,如配置加载、依赖注入只执行一次:在子应用生命周期中只调用一次示例:export function bootstrap() { console.log('子应用初始化'); return Promise.resolve();}2. mount(挂载)触发时机:子应用需要渲染到页面时作用:将子应用渲染到指定的容器中可多次调用:每次路由切换到该子应用时都会触发示例:export function mount(container) { ReactDOM.render(<App />, container); return Promise.resolve();}3. unmount(卸载)触发时机:子应用需要从页面移除时作用:清理子应用的 DOM、事件监听、定时器等资源必须执行:确保完全清理,避免内存泄漏示例:export function unmount(container) { ReactDOM.unmountComponentAtNode(container); return Promise.resolve();}4. update(更新)触发时机:子应用需要更新时(可选)作用:处理子应用的更新逻辑非必需:不是所有子应用都需要实现示例:export function update(props) { // 处理属性更新 return Promise.resolve();}生命周期流程首次加载:bootstrap → mount路由切换:unmount(旧应用)→ mount(新应用)重新激活:直接调用 mount(不重复 bootstrap)完全卸载:unmount + 清理所有资源最佳实践异步处理:所有生命周期函数都应返回 Promise错误处理:在生命周期中添加错误捕获逻辑资源清理:在 unmount 中彻底清理所有副作用性能优化:避免在 mount 中执行耗时操作状态管理:合理管理子应用的状态,避免重复初始化通过合理使用生命周期钩子,可以确保子应用的稳定运行和资源的有效管理。
阅读 0·2月21日 16:01

如何在 Hardhat 中编写智能合约测试?

Hardhat 提供了强大的测试框架,基于 Mocha 和 Chai,以下是编写测试的核心要点:测试文件结构:const { expect } = require("chai");const { ethers } = require("hardhat");describe("MyContract", function () { beforeEach(async function () { const MyContract = await ethers.getContractFactory("MyContract"); this.contract = await MyContract.deploy(); await this.contract.deployed(); }); it("should return the correct value", async function () { expect(await this.contract.getValue()).to.equal(42); });});核心功能:合约部署const Contract = await ethers.getContractFactory("ContractName");const contract = await Contract.deploy();await contract.deployed();函数调用和断言const tx = await contract.someFunction(param1, param2);await tx.wait(); // 等待交易确认expect(await contract.someViewFunction()).to.equal(expectedValue);事件监听await expect(contract.someFunction()) .to.emit(contract, "EventName") .withArgs(arg1, arg2);交易回滚测试await expect(contract.failingFunction()) .to.be.revertedWith("Error message");快照功能const snapshot = await ethers.provider.send("evm_snapshot", []);// 执行一些操作await ethers.provider.send("evm_revert", [snapshot]);时间操作await ethers.provider.send("evm_increaseTime", [3600]); // 增加1小时await ethers.provider.send("evm_mine"); // 挖掘新区块最佳实践:使用 beforeEach 和 afterEach 管理测试状态为每个测试用例提供清晰的描述测试正常路径和异常路径使用有意义的测试数据保持测试的独立性和可重复性
阅读 0·2月21日 15:59

Hardhat 如何支持 TypeScript 和类型安全?

Hardhat 对 TypeScript 提供了原生支持,以下是使用 TypeScript 的主要优势和方法:1. 项目初始化使用 TypeScript 模板创建项目:npx hardhat init# 选择 "Create a TypeScript project"2. 类型安全的合约交互Hardhat 自动生成类型定义:import { ethers } from "hardhat";async function main() { const Contract = await ethers.getContractFactory("MyContract"); const contract = await Contract.deploy() as MyContract; // 类型安全的函数调用 await contract.setValue(42); const value = await contract.value(); console.log("Value:", value.toNumber());}3. 使用 TypeChainTypeChain 为合约 ABI 生成类型定义:npm install --save-dev typechain @typechain/ethers-v5在 hardhat.config.ts 中配置:import "@typechain/hardhat";生成的类型定义:import { MyContract } from "../typechain-types";const contract: MyContract = await contractFactory.deploy();4. 测试中的类型安全import { expect } from "chai";import { ethers } from "hardhat";import { MyContract } from "../typechain-types";describe("MyContract", function () { let contract: MyContract; beforeEach(async function () { const ContractFactory = await ethers.getContractFactory("MyContract"); contract = await ContractFactory.deploy(); await contract.deployed(); }); it("should set value correctly", async function () { await contract.setValue(100); expect(await contract.value()).to.equal(100); });});5. 配置文件类型安全使用 TypeScript 配置文件 hardhat.config.ts:import { HardhatUserConfig } from "hardhat/config";import "@nomicfoundation/hardhat-toolbox";const config: HardhatUserConfig = { solidity: "0.8.19", networks: { sepolia: { url: process.env.SEPOLIA_RPC_URL || "", accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] } }};export default config;6. 环境变量类型定义// .env.d.tsdeclare namespace NodeJS { interface ProcessEnv { SEPOLIA_RPC_URL: string; PRIVATE_KEY: string; ETHERSCAN_API_KEY: string; }}优势:编译时类型检查智能代码补全重构更安全减少运行时错误更好的代码文档团队协作更高效
阅读 0·2月21日 15:59

Hardhat 主网分叉功能如何使用?

Hardhat 主网分叉功能允许开发者基于以太坊主网或测试网的当前状态创建本地开发环境,这对于测试 DeFi 协议和与现有合约交互非常有用。基本用法:在 hardhat.config.js 中配置分叉:networks: { hardhat: { forking: { url: process.env.MAINNET_RPC_URL, blockNumber: 15000000 // 可选:指定分叉区块 } }}使用场景:测试与主网合约的交互const uniswapRouter = await ethers.getContractAt( "IUniswapV2Router02", "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D");模拟真实市场条件// 获取主网上的代币价格const dai = await ethers.getContractAt("IERC20", DAI_ADDRESS);const price = await someOracle.getPrice(dai.address);测试 DeFi 协议集成// 在分叉环境中测试 Aave 集成const pool = await ethers.getContractAt( "IPool", "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2");高级配置:networks: { hardhat: { forking: { url: process.env.MAINNET_RPC_URL, blockNumber: 15000000, enabled: true }, chainId: 1 // 保持与主网相同的 chainId }}测试中的使用:describe("Mainnet Fork Tests", function () { it("should interact with Uniswap", async function () { // 获取测试账户 const [signer] = await ethers.getSigners(); // 获取主网 USDC const usdc = await ethers.getContractAt("IERC20", USDC_ADDRESS); const balance = await usdc.balanceOf(signer.address); console.log("USDC Balance:", balance.toString()); });});注意事项:需要 RPC 节点支持归档数据分叉会增加内存使用确保有足够的测试 ETH考虑使用固定的区块号以确保测试可重复性最佳实践:使用环境变量存储 RPC URL指定固定的区块号以确保测试稳定性在 CI/CD 中使用归档节点定期更新分叉区块号
阅读 0·2月21日 15:59

什么是 Hardhat Ignition 及其使用方法?

Hardhat Ignition 是 Hardhat 的声明式部署系统,提供了更强大和可维护的部署方式:核心概念:模块化部署使用模块定义部署逻辑支持模块间的依赖关系声明式配置而非命令式脚本部署状态管理自动跟踪部署状态支持增量部署避免重复部署基本使用:创建部署模块:const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");module.exports = buildModule("TokenModule", (m) => { const token = m.contract("MyToken", ["MyToken", "MTK", 18]); return { token };});高级功能:参数化部署module.exports = buildModule("TokenModule", (m) => { const name = m.getParameter("name", "MyToken"); const symbol = m.getParameter("symbol", "MTK"); const token = m.contract("MyToken", [name, symbol, 18]); return { token };});依赖管理module.exports = buildModule("DAppModule", (m) => { const token = m.contract("MyToken"); const sale = m.contract("TokenSale", [token]); // 调用 token 合约的函数 m.call(token, "transferOwnership", [sale]); return { token, sale };});现有合约使用module.exports = buildModule("Module", (m) => { const existingContract = m.contractAt( "ExistingContract", "0x1234..." ); return { existingContract };});部署命令:# 部署到本地网络npx hardhat ignition deploy ./ignition/modules/Module.js# 部署到测试网npx hardhat ignition deploy ./ignition/modules/Module.js --network sepolia# 使用参数部署npx hardhat ignition deploy ./ignition/modules/Module.js --parameters name:CustomToken,symbol:CTK# 验证部署npx hardhat ignition deploy ./ignition/modules/Module.js --verify部署计划:Ignition 会生成部署计划,显示将要执行的操作:npx hardhat ignition plan ./ignition/modules/Module.js优势:声明式配置更易理解自动处理部署依赖支持部署验证更好的错误处理适合复杂的多合约部署便于团队协作
阅读 0·2月21日 15:59

JavaScript 如何操作和操作 SVG

JavaScript 与 SVG 的结合可以实现强大的动态交互功能。以下是 JavaScript 操作 SVG 的主要方法:1. 选择 SVG 元素使用标准 DOM 方法选择 SVG 元素。// 通过 ID 选择const circle = document.getElementById('myCircle');// 通过类名选择const circles = document.querySelectorAll('.circle');// 通过标签名选择const allRects = document.querySelectorAll('rect');// 通过属性选择const filledElements = document.querySelectorAll('[fill="red"]');2. 创建 SVG 元素使用 createElementNS 创建 SVG 元素(注意命名空间)。const svgNS = 'http://www.w3.org/2000/svg';// 创建 SVG 元素const circle = document.createElementNS(svgNS, 'circle');circle.setAttribute('cx', '100');circle.setAttribute('cy', '100');circle.setAttribute('r', '50');circle.setAttribute('fill', 'blue');// 添加到 SVGconst svg = document.querySelector('svg');svg.appendChild(circle);3. 修改 SVG 属性使用 setAttribute 和 getAttribute 方法。const circle = document.querySelector('circle');// 修改属性circle.setAttribute('fill', 'red');circle.setAttribute('r', '60');circle.setAttribute('stroke', 'black');circle.setAttribute('stroke-width', '3');// 获取属性const fill = circle.getAttribute('fill');const radius = circle.getAttribute('r');4. 修改 SVG 样式使用 style 属性或 classList。const circle = document.querySelector('circle');// 直接设置样式circle.style.fill = 'green';circle.style.opacity = '0.5';circle.style.transform = 'scale(1.2)';// 使用 classListcircle.classList.add('highlight');circle.classList.remove('normal');circle.classList.toggle('active');5. 事件监听为 SVG 元素添加事件监听器。const circle = document.querySelector('circle');// 鼠标事件circle.addEventListener('click', function() { console.log('Circle clicked!'); this.setAttribute('fill', 'red');});circle.addEventListener('mouseover', function() { this.style.cursor = 'pointer';});circle.addEventListener('mouseout', function() { this.style.cursor = 'default';});// 键盘事件(需要 tabindex)circle.setAttribute('tabindex', '0');circle.addEventListener('keydown', function(event) { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); console.log('Circle activated!'); }});6. 动画实现使用 JavaScript 实现 SVG 动画。const circle = document.querySelector('circle');// 使用 requestAnimationFramelet progress = 0;function animate() { progress += 0.01; const x = 100 + Math.sin(progress * 2 * Math.PI) * 50; circle.setAttribute('cx', x); if (progress < 1) { requestAnimationFrame(animate); }}animate();// 使用 CSS 过渡circle.style.transition = 'all 0.5s ease';circle.setAttribute('fill', 'red');circle.setAttribute('r', '60');7. 获取鼠标位置获取鼠标在 SVG 中的相对位置。const svg = document.querySelector('svg');svg.addEventListener('click', function(event) { const point = svg.createSVGPoint(); point.x = event.clientX; point.y = event.clientY; const svgPoint = point.matrixTransform(svg.getScreenCTM().inverse()); console.log(`SVG coordinates: x=${svgPoint.x}, y=${svgPoint.y}`);});8. 拖拽功能实现 SVG 元素的拖拽。let selectedElement = null;let offset = { x: 0, y: 0 };function getMousePosition(evt) { const CTM = svg.getScreenCTM(); return { x: (evt.clientX - CTM.e) / CTM.a, y: (evt.clientY - CTM.f) / CTM.d };}function startDrag(evt) { selectedElement = evt.target; offset = getMousePosition(evt); offset.x -= parseFloat(selectedElement.getAttribute('cx')); offset.y -= parseFloat(selectedElement.getAttribute('cy'));}function drag(evt) { if (selectedElement) { evt.preventDefault(); const coord = getMousePosition(evt); selectedElement.setAttribute('cx', coord.x - offset.x); selectedElement.setAttribute('cy', coord.y - offset.y); }}function endDrag(evt) { selectedElement = null;}const svg = document.querySelector('svg');svg.addEventListener('mousedown', startDrag);svg.addEventListener('mousemove', drag);svg.addEventListener('mouseup', endDrag);svg.addEventListener('mouseleave', endDrag);9. 动态创建复杂图形使用 JavaScript 动态创建复杂的 SVG 图形。function createStar(cx, cy, spikes, outerRadius, innerRadius) { const svgNS = 'http://www.w3.org/2000/svg'; const polygon = document.createElementNS(svgNS, 'polygon'); let points = []; for (let i = 0; i < spikes * 2; i++) { const radius = i % 2 === 0 ? outerRadius : innerRadius; const angle = (Math.PI / spikes) * i; const x = cx + Math.cos(angle) * radius; const y = cy + Math.sin(angle) * radius; points.push(`${x},${y}`); } polygon.setAttribute('points', points.join(' ')); polygon.setAttribute('fill', 'gold'); polygon.setAttribute('stroke', 'orange'); polygon.setAttribute('stroke-width', '2'); return polygon;}const svg = document.querySelector('svg');const star = createStar(100, 100, 5, 50, 25);svg.appendChild(star);10. 数据可视化使用 SVG 和 JavaScript 创建数据可视化。const data = [10, 25, 40, 30, 50];const svg = document.querySelector('svg');const barWidth = 40;const gap = 20;data.forEach((value, index) => { const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); const x = 20 + index * (barWidth + gap); const y = 200 - value * 3; rect.setAttribute('x', x); rect.setAttribute('y', y); rect.setAttribute('width', barWidth); rect.setAttribute('height', value * 3); rect.setAttribute('fill', `hsl(${index * 60}, 70%, 50%)`); svg.appendChild(rect);});最佳实践:使用 createElementNS 创建 SVG 元素合理使用事件委托优化动画性能,使用 requestAnimationFrame注意命名空间考虑使用 SVG 库(如 D3.js)处理复杂场景测试跨浏览器兼容性
阅读 0·2月21日 15:58

Expo应用如何实现国际化(i18n)?有哪些推荐的库?

Expo应用的国际化(i18n)是面向全球用户的重要功能。Expo支持多种国际化解决方案,使开发者能够轻松实现多语言支持。国际化库选择:i18next最流行的国际化库,功能强大且易于使用。安装:npm install i18next react-i18next expo-localization配置i18next:// i18n.tsimport i18n from 'i18next';import { initReactI18next } from 'react-i18next';import { getLocales } from 'expo-localization';const resources = { en: { translation: { welcome: 'Welcome', login: 'Login', logout: 'Logout', 'hello.name': 'Hello, {{name}}!', }, }, zh: { translation: { welcome: '欢迎', login: '登录', logout: '退出', 'hello.name': '你好,{{name}}!', }, },};i18n .use(initReactI18next) .init({ resources, lng: getLocales()[0]?.languageCode || 'en', fallbackLng: 'en', interpolation: { escapeValue: false, }, });export default i18n;使用i18next:import { useTranslation } from 'react-i18next';function WelcomeScreen() { const { t, i18n } = useTranslation(); const changeLanguage = (lang: string) => { i18n.changeLanguage(lang); }; return ( <View> <Text>{t('welcome')}</Text> <Text>{t('hello.name', { name: 'John' })}</Text> <Button title="English" onPress={() => changeLanguage('en')} /> <Button title="中文" onPress={() => changeLanguage('zh')} /> </View> );}expo-localizationExpo官方的本地化库,用于获取设备语言设置。使用expo-localization:import * as Localization from 'expo-localization';function getDeviceLanguage() { const locale = Localization.locale; const languageCode = Localization.locale.split('-')[0]; console.log('Locale:', locale); console.log('Language:', languageCode); return languageCode;}获取本地化信息:function getLocalizationInfo() { const locale = Localization.locale; const timezone = Localization.timezone; const isoCurrencyCodes = Localization.isoCurrencyCodes; return { locale, timezone, currency: isoCurrencyCodes[0], };}React Native Localization轻量级的国际化解决方案。安装:npm install react-native-localize配置:import * as RNLocalize from 'react-native-localize';const translations = { en: require('./en.json'), zh: require('./zh.json'),};const fallback = { languageTag: 'en', isRTL: false };const { languageTag } = RNLocalize.findBestAvailableLanguage(Object.keys(translations)) || fallback;i18n.init({ resources: { [languageTag]: translations[languageTag], }, lng: languageTag, fallbackLng: 'en',});多语言资源管理:JSON文件结构// locales/en.json{ "common": { "ok": "OK", "cancel": "Cancel", "save": "Save" }, "auth": { "login": "Login", "logout": "Logout", "register": "Register", "forgotPassword": "Forgot Password?" }, "home": { "title": "Home", "welcome": "Welcome back!", "recentActivity": "Recent Activity" }}// locales/zh.json{ "common": { "ok": "确定", "cancel": "取消", "save": "保存" }, "auth": { "login": "登录", "logout": "退出", "register": "注册", "forgotPassword": "忘记密码?" }, "home": { "title": "首页", "welcome": "欢迎回来!", "recentActivity": "最近活动" }}命名空间组织// 使用命名空间i18n.init({ resources: { en: { common: require('./locales/en/common.json'), auth: require('./locales/en/auth.json'), home: require('./locales/en/home.json'), }, zh: { common: require('./locales/zh/common.json'), auth: require('./locales/zh/auth.json'), home: require('./locales/zh/home.json'), }, },});// 使用命名空间const { t } = useTranslation('common');<Text>{t('ok')}</Text>// 跨命名空间<Text>{t('auth:login')}</Text>日期和时间本地化:import { format } from 'date-fns';import { zhCN, enUS } from 'date-fns/locale';function formatDate(date: Date, locale: string) { const localeMap = { en: enUS, zh: zhCN, }; return format(date, 'PPP', { locale: localeMap[locale] || enUS, });}function formatDateTime(date: Date, locale: string) { return format(date, 'PPPppp', { locale: locale === 'zh' ? zhCN : enUS, });}数字和货币本地化:function formatCurrency(amount: number, locale: string) { return new Intl.NumberFormat(locale, { style: 'currency', currency: locale === 'zh' ? 'CNY' : 'USD', }).format(amount);}function formatNumber(number: number, locale: string) { return new Intl.NumberFormat(locale).format(number);}function formatPercent(value: number, locale: string) { return new Intl.NumberFormat(locale, { style: 'percent', minimumFractionDigits: 2, }).format(value);}RTL(从右到左)支持:import { I18nManager } from 'react-native';function setupRTL(locale: string) { const isRTL = locale === 'ar' || locale === 'he'; if (I18nManager.isRTL !== isRTL) { I18nManager.allowRTL(isRTL); I18nManager.forceRTL(isRTL); // 需要重启应用 Updates.reloadAsync(); }}动态语言切换:function LanguageSwitcher() { const { i18n } = useTranslation(); const [currentLang, setCurrentLang] = useState(i18n.language); const changeLanguage = async (lang: string) => { await i18n.changeLanguage(lang); setCurrentLang(lang); // 保存用户偏好 await AsyncStorage.setItem('userLanguage', lang); // 如果需要RTL支持 setupRTL(lang); }; const languages = [ { code: 'en', name: 'English' }, { code: 'zh', name: '中文' }, { code: 'es', name: 'Español' }, ]; return ( <View> {languages.map((lang) => ( <Button key={lang.code} title={lang.name} onPress={() => changeLanguage(lang.code)} disabled={currentLang === lang.code} /> ))} </View> );}最佳实践:资源文件组织按功能模块组织翻译文件使用命名空间避免冲突保持翻译文件结构一致翻译质量使用专业的翻译工具考虑文化差异和习惯定期审查和更新翻译性能优化按需加载语言包缓存翻译结果避免频繁的语言切换用户体验自动检测设备语言提供语言切换选项保存用户语言偏好测试覆盖测试所有语言的显示检查文本溢出问题验证RTL布局常见问题:翻译缺失设置fallback语言使用翻译管理工具定期检查翻译完整性文本溢出使用flex布局提供文本截断选项为不同语言调整布局动态加载使用代码分割懒加载语言包预加载常用语言通过完善的国际化实现,Expo应用可以更好地服务全球用户,提升用户体验和市场竞争力。
阅读 0·2月21日 15:58