阅读视图

发现新文章,点击刷新页面。

React-router v7 第四章(路由传参)

参数传递

React-router 一共有三种方式进行参数传递,参数传递指的是在路由跳转时,将参数传递给目标路由。

Query方式

Query的方式就是使用 ? 来传递参数,例如:

#多个参数用 & 连接
/user?name=小满zs&age=18

跳转方式:

<NavLink  to="/about?id=123">About</NavLink> //1. NavLink 跳转
<Link to="/about?id=123">About</Link> //2. Link 跳转
import { useNavigate } from 'react-router'
const navigate = useNavigate()
navigate('/about?id=123') //3. useNavigate 跳转

获取参数:

//1. 获取参数
import { useSearchParams } from 'react-router'
const [searchParams, setSearchParams] = useSearchParams()
console.log(searchParams.get('id')) //获取id参数

//2. 获取参数
import { useLocation } from 'react-router'
const { search } = useLocation()
console.log(search) //获取search参数 ?id=123

Params方式

Params的方式就是使用 :[name] 来传递参数,例如:

/user/:id

跳转方式:

<NavLink to="/user/123">User</NavLink> //1. NavLink 跳转
<Link to="/user/123">User</Link> //2. Link 跳转
import { useNavigate } from 'react-router'
const navigate = useNavigate()
navigate('/user/123') //3. useNavigate 跳转

获取参数:

import { useParams } from 'react-router'
const { id } = useParams()
console.log(id) //获取id参数

State方式

state在URL中不显示,但是可以传递参数,例如:

/user

跳转方式:

<Link to="/user" state={{ name: '小满zs', age: 18 }}>User</Link> //1. Link 跳转
<NavLink to="/user" state={{ name: '小满zs', age: 18 }}>User</NavLink> //2. NavLink 跳转
import { useNavigate } from 'react-router'
const navigate = useNavigate()
navigate('/user', { state: { name: '小满zs', age: 18 } }) //3. useNavigate 跳转

获取参数:

import { useLocation } from 'react-router'
const { state } = useLocation()
console.log(state) //获取state参数
console.log(state.name) //获取name参数
console.log(state.age) //获取age参数

总结

React Router 提供了三种参数传递方式,各有特点:

1. Params 方式 (/user/:id)

  • 适用于:传递必要的路径参数(如ID)
  • 特点:符合 RESTful 规范,刷新不丢失
  • 限制:只能传字符串,参数显示在URL中

2. Query 方式 (/user?name=xiaoman)

  • 适用于:传递可选的查询参数
  • 特点:灵活多变,支持多参数
  • 限制:URL可能较长,参数公开可见

3. State 方式

  • 适用于:传递复杂数据结构
  • 特点:支持任意类型数据,参数不显示在URL
  • 限制:刷新可能丢失,不利于分享

选择建议:必要参数用 Params,筛选条件用 Query,临时数据用 State。

聊聊双列瀑布流

今天我们来聊聊实现一个双列瀑布流 效果展示 什么是双列瀑布流? 双列瀑布流布局是一种常见的网页设计布局,通常用于展示图片、文章或其他内容

flutter工程化之动态配置

动机

很多公司在实际开发中都有不少动态化配置的需求,比如说要根据不同app渠道加载不同的URL、secrets等配置项。这其中的实现方法也有很多,今天的应用案例则是--dart-define--dart-define-from-file

目标

  • 一处配置多处使用
  • 尽量少用第三方库
  • 非入侵

用法

--dart-define

使用方法简单粗暴,直接把如下的命令添加上就好,无论是flutter build还是flutter build都可以:

 --dart-define API_URL=api.openflutter.dev

如果有需要添加多个变量也是可以的:

 --dart-define API_URL=api.openflutter.dev  --dart-define MAP_KEY=ds5jkh2jjhjkljh

如果我有大量的变量怎么办?

--dart-define-from-file

我们也可以从文件中加载配置文件,文件格式可以是json也可以是.env:

--dart-define-from-file path/to/config.json
--dart-define-from-file path/to/.env

Flutter端获取变量

很简单:


final url = const String.fromEnvironment("API_URL");
final logEnabled = const bool.fromEnvironment("LOG_ENABLED");
final level = const bool.fromEnvironment("LEVEL");

补充一句:

千万别忘了写const

如果你使用的.env文件做为配置项,你也可以使用一个代码叫envied的代码生成工具,以简化相关操作。

但很多时候仅仅在Flutter端获取这些配置是不够的,我们也会需要在Android侧和iOS侧获取相关配置。

Android 端

找到app/build.gradle.kts,旧的Flutter应该是app/build.gradle,然后添加一些代码:

val dartDefines = mutableMapOf<String, String>()

if (project.hasProperty("dart-defines")) {
    project.property("dart-defines").toString().split(",").forEach { entry ->
        val decoded = String(Base64.getDecoder().decode(entry), Charsets.UTF_8)
        val pair = decoded.split("=")
        if (pair.size == 2) {
            dartDefines[pair[0]] = pair[1]
        }
    }
}


defaultConfig {
    applicationId = "com.example.flutter_dash_testing"
    minSdk = flutter.minSdkVersion
    targetSdk = flutter.targetSdkVersion
    versionCode = flutter.versionCode
    versionName = flutter.versionName
    dartDefines["API_URL"]?.let {
        resValue("string", "api_url", it)
    }
}

iOS侧

当我们用 Flutter 编译 iOS 端时,会在./ios/Flutter目录下生成两个文件Generated.xcconfigflutter_export_environment.sh。 在这两个文件中我们都会过来的dart-define:

// flutter_export_environment.sh
export "DART_DEFINES=S0VZPWtleSBpcyB0ZXN0aW5n,QVBJX1VSTD1odHRwczovL2FwaS5leGFtcGxlLmNvbQ==,QVBQX05BTUU9SGVsbG8gV29ybGQ=,RkxVVFRFUl9BUFBfRkxBVk9SPXN0YWdpbmc="

// Generated.xcconfig
DART_DEFINES=S0VZPWtleSBpcyB0ZXN0aW5n,QVBJX1VSTD1odHRwczovL2FwaS5leGFtcGxlLmNvbQ==,QVBQX05BTUU9SGVsbG8gV29ybGQ=,RkxVVFRFUl9BUFBfRkxBVk9SPXN0YWdpbmc=

首先我们需要把这些经过base64编码过的变量解析到某个.xcconfig中,然后再把该文件在Release.xcconfigDebug.xcconfig引入,假设它的名字是GeneratedDartDefines.xcconfig:

// ./ios/Flutter/Debug.xcconfig

#include "Generated.xcconfig"
#include "GeneratedDartDefines.xcconfig"

// ./ios/Flutter/Release.xcconfig

#include "Generated.xcconfig"
#include "GeneratedDartDefines.xcconfig"

假设我们要生成的文件名字为GeneratedDartDefines.xcconfig。首先我们在./ios/Flutter目录下生成一个空的GeneratedDartDefines.xcconfig。然后打开Xcode,在Targets=>Runner=>Build Phases选项卡中点击+号,选择New Run Script Phase并把如下代码复制进去:

DART_DEFINES=$(cat "${SRCROOT}/Flutter/Generated.xcconfig" | grep "DART_DEFINES" | sed -E 's/DART_DEFINES=(.+)/\1/')

IFS=',' read -ra DEFINES <<< "$DART_DEFINES"
ALL_DECODED=""

for DEFINE in "${DEFINES[@]}"; do
    # Decode Base64
    DECODED=$(echo "$DEFINE" | base64 --decode)
    echo "DECODED $DECODED"
    # Skip Flutter internal variables
    if [[ "$DECODED" != FLUTTER_* ]]; then
        if [ -z "$ALL_DECODED" ]; then
            ALL_DECODED="$DECODED"
        else
            # Use proper newline insertion
            ALL_DECODED="${ALL_DECODED}"$'\n'"${DECODED}"
        fi
    fi
done

echo "$ALL_DECODED" > "${SRCROOT}/Flutter/GeneratedDartDefines.xcconfig"

最后,按需修改下Info.plist即可,如修改app名字:

// ./ios/Runner/Info.plist
// ...
<key>CFBundleName</key>
<string>$(APP_NAME)</string>
// ...

开发具怎么配置

Android Studio

在绿色的运行按钮左侧有个下拉框,然后点击Edit Configurations ,其中有一个项目叫做Addtional run args,把--dart-define-from-file .env类似的参数添加进去就好了。当然你也可以选择保存配置,这样大家就可以共享这个快捷键了。

VSCode

改改launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Flutter Dev",
      "request": "launch",
      "type": "dart",
      "flutterMode": "debug",
      "args": [
        "--dart-define=API_URL=https://api.dev.example.com",
        "--dart-define=APP_ENVIRONMENT=development",
        "--flavor=dev"
      ]
    },
    {
      "name": "Flutter Staging",
      "request": "launch",
      "type": "dart",
      "flutterMode": "debug",
      "args": [
        "--dart-define-from-file=.env",
        "--flavor=staging"
      ]
    }
  ]
}

UniApp金融理财产品项目简单介绍

一、项目目录架构设计 二、核心模块实现详解 1. 产品展示模块 ‌业务流程图: ‌ ‌关键实现代码: ‌ ‌优化点: ‌ 实现分页加载和虚拟滚动‌ 产品卡片使用骨架屏优化体验‌ 收益率数字动画效果‌

SvelteKit 最新中文文档教程(20)—— 最佳实践之性能

前言

Svelte,一个语法简洁、入门容易,面向未来的前端框架。

从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1

image.png

Svelte 以其独特的编译时优化机制著称,具有轻量级高性能易上手等特性,非常适合构建轻量级 Web 项目

为了帮助大家学习 Svelte,我同时搭建了 Svelte 最新的中文文档站点。

如果需要进阶学习,也可以入手我的小册《Svelte 开发指南》,语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”

性能

SvelteKit 开箱即用地做了很多工作来使您的应用程序尽可能高效:

  • 代码分割,因此只加载当前页面所需的代码
  • 资源预加载,防止出现"瀑布(waterfalls)"(文件请求其他文件的情况)
  • 文件哈希,使您的资源可以永久缓存
  • 请求合并,将从不同服务端 load 函数获取的数据合并成单个 HTTP 请求
  • 并行加载,不同的通用 load 函数同时获取数据
  • 数据内联,服务端渲染期间使用 fetch 发出的请求可以在浏览器中重放而无需重新发出请求
  • 保守的失效处理,因此 load 函数仅在必要时重新运行
  • 预渲染(如有必要,可按路由配置),使没有动态数据的页面可以立即提供服务
  • 链接预加载,以便提前主动获取客户端导航所需的数据和代码

尽管如此,我们(尚)不能消除所有导致性能下降的因素。要获得最大性能,您应该注意以下建议。

诊断问题

Google 的 PageSpeed Insights 和(用于更高级分析的)WebPageTest 是了解已部署到互联网的网站性能特征的出色工具。

您的浏览器还包含有用的开发者工具,用于分析您的网站,无论是已部署还是在本地运行:

请注意,在 dev 模式下本地运行的网站会表现出与生产应用程序不同的行为,因此您应该在构建后在预览模式下进行性能测试。

检测

如果您在浏览器的网络标签中看到 API 调用耗时过长,想了解原因,您可以考虑使用 OpenTelemetryServer-Timing 头 等工具来检测您的后端。

优化资源

图片

减小图片文件的大小通常是对网站性能影响最大的改变之一。Svelte 提供了 @sveltejs/enhanced-img 包,详见图片页面,可以让这个过程更容易。此外,Lighthouse 对识别性能影响最大的图片很有帮助。

视频

视频文件可能非常大,因此应特别注意确保它们经过优化:

  • 使用 Handbrake 等工具压缩视频。考虑将视频转换为 .webm.mp4 等 web 友好的格式。
  • 您可以使用 preload="none" 对折叠区域以下的视频进行延迟加载(但请注意,这会在用户开始播放时减慢播放速度)。
  • 使用 FFmpeg 等工具从静音视频中删除音轨。

字体

当用户访问页面时,SvelteKit 会自动预加载关键的 .js.css 文件,但默认情况下不会预加载字体,因为这可能会导致下载不必要的文件(例如 CSS 中引用但实际上在当前页面上未使用的字体粗细)。话虽如此,正确预加载字体可以对网站的速度感知产生很大影响。在您的 handle hook 中,您可以使用包含字体的 preload 过滤器调用 resolve

通过子集化字体,您可以减小字体文件的大小。

减少代码大小

Svelte 版本

我们推荐使用最新版本的 Svelte。Svelte 5 比 Svelte 4 更小更快,而 Svelte 4 又比 Svelte 3 更小更快。

rollup-plugin-visualizer 可以帮助识别哪些包对网站大小贡献最大。您还可以通过手动检查构建输出发现删除代码的机会(在您的 Vite 配置中使用 build: { minify: false } 使输出可读,但记得在部署应用程序之前撤销此更改),或通过浏览器开发工具的网络标签。

外部脚本

尽量减少在浏览器中运行的第三方脚本数量。例如,与其使用基于 JavaScript 的分析工具,不如考虑使用服务端实现,比如许多带有 SvelteKit 适配器的平台,包括 CloudflareNetlifyVercel 所提供的解决方案。

要在 Web Worker 中运行第三方脚本(避免阻塞主线程),请使用 Partytown 的 SvelteKit 集成

选择性加载

使用静态 import 声明导入的代码将自动与页面的其余部分打包在一起。如果某段代码仅在满足特定条件时才需要,请使用动态 import(...) 形式来选择性地延迟加载组件。

导航

预加载

您可以使用链接选项预先加载必要的代码和数据来加快客户端导航。当您创建新的 SvelteKit 应用程序时,<body> 元素上是默认有配置的。

非必要数据

对于加载缓慢且不需要立即使用的数据,从 load 函数返回的对象可以包含 promise 而不是数据本身。对于服务端 load 函数,这将导致数据在导航(或初始页面加载)后流式传输

防止瀑布问题

最大的性能杀手之一是所谓的瀑布问题,即一系列按顺序发出的请求。这可能发生在服务端或浏览器中。

  • 当您的 HTML 请求 JS,然后请求 CSS,然后请求背景图片和网络字体时,可能会在浏览器中出现资源瀑布。SvelteKit 通过添加 modulepreload 标签或头部,将为您解决这类问题,但您应该查看开发工具中的网络标签以检查是否需要预加载其他资源。如果您使用网络字体,请特别注意这一点,因为它们需要手动处理。
  • 如果通用 load 函数发起 API 调用来获取当前用户,然后使用该响应中的详细信息来获取已保存项目的列表,然后使用该响应来获取每个项目的详细信息,浏览器最终将发出多个顺序请求。这对性能来说是致命的,尤其是对于物理位置远离您后端的用户。在可能的情况下使用服务端 load 函数来避免这个问题。
  • 服务端 load 函数也不能免疫瀑布问题(尽管它们的代价要小得多,因为它们很少涉及高延迟的往返)。例如,如果您查询数据库以获取当前用户,然后使用该数据进行第二次查询以获取已保存项目的列表,使用带有数据库连接的单个查询通常会更高效。

托管

为了最小化延迟,你的前端应该与后端位于同一数据中心。对于没有中央后端的站点,许多 SvelteKit 适配器支持部署到 edge,这意味着从离用户最近的服务器处理每个用户的请求。这可以显著减少加载时间。一些适配器甚至支持按路由配置部署。您还应该考虑从 CDN(通常是 edge 网络)提供图片服务 — 许多 SvelteKit 适配器的主机会自动完成这项工作。

确保您的主机使用 HTTP/2 或更新版本。Vite 的代码分割创建了许多小文件以提高缓存能力,这会带来出色的性能表现,但这建立在您的文件可以通过 HTTP/2 并行加载的前提下。

延伸阅读

在大多数情况下,构建高性能的 SvelteKit 应用程序与构建任何高性能的 Web 应用程序是一样的。您应该能够将这些来自通用性能资源(如 Core Web Vitals)的信息应用到任何您构建的 Web 体验中。

Svelte 中文文档

点击查看中文文档:SvelteKit 性能

系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

此外我还写过 JavaScript 系列TypeScript 系列React 系列Next.js 系列冴羽答读者问等 14 个系列文章, 全系列文章目录:github.com/mqyqingfeng…

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”

面试官:谈一下在 ts 中你对 any 和 unknow 的理解

 前言

在开发中,尤其在 react 项目中,一般都是会使用 ts 的,但是如果 ts 使用不当,就会出现很多any,也就是很多人笑称 typescript = anyscript,例如下面这段代码:

export interface Person {
  name: any;
  age: any;
  sex: any;
}

很多时候使用一些简单的类型定义,比如 number、boolean、string 可能我们还想写一下,但是遇到一些复杂的比如对象、promise,由于影响写代码的速度就直接使用 any 代替了,当然这样确实比较方便,但是一味的使用 any,ts 就失去了意义,尽管 ts 官方鼓励大家使用 any。

而且有的领导在 cr 时,很讨厌看到 any,如果有 any 是不给通过的。

那和any相似的还有一个unknow,本文就为大家简单介绍一下,这两者的区别。

any

首先,让我们来看看 any 类型。any 可以说是 ts 中最灵活的类型,它允许我们对该类型的值进行任何操作,而不会触发类型检查。这样我们就可以将任何类型的值赋给 any 类型的变量,也可以将 any 类型的值赋给其他任何类型的变量。这种灵活性就使得 any 成为了一把双刃剑。

举个栗子:

let myAny: any = 42;
myAny = "Hello, world!";
myAny = true;
myAny = [1, 2, 3];

let myNumber: number = myAny; // 不会报错
console.log(myAny.nonExistentMethod()); // 不会在编译时报错,但可能在运行时报错

在这个栗子中,我们可以看到 any 类型的变量可以被赋予任何类型的值,而且可以被赋给其他类型的变量,甚至竟然还可以调用不存在的方法而不会在编译时报错。这种自由度虽然方便,但也容易导致潜在的运行时错误。

那么相比之下,unknown 类型则更加严格和安全。unknown 可以被认为是类型安全版本的 any。它同样可以接受任何类型的值,但是在使用 unknown 类型的值时,我们必须先进行类型检查或类型断言。

unknown

来看一个 unknown 的栗子:

let myUnknown: unknown = 42;
myUnknown = "Hello, world!";
myUnknown = true;
myUnknown = [1, 2, 3];

// let myNumber: number = myUnknown; // 这行会报错
// console.log(myUnknown.length); // 这行也会报错

if (typeof myUnknown === "string") {
    console.log(myUnknown.toUpperCase());
}

let myString: string = myUnknown as string; // 使用类型断言

在这个栗子中,我们可以看到 unknown 类型的变量同样可以接受任何类型的值。但是,我们不能直接将 unknown 类型的值赋给其他类型的变量,也不能直接访问 unknown 类型值的属性或方法。我们必须先进行类型检查( if 语句中的类型检查)或使用类型断言 as

什么情况下使用 any 或者 unknow

那么,我们应该在什么情况下使用 any,什么情况下使用 unknown 呢?

当我们真的不知道变量的类型,并且需要在代码中频繁地使用该变量而不进行类型检查时,可以使用 any。比如在处理来自第三方库的数据,或者在迁移 js 项目到 ts 的过程中,any 可能会很有用。

但是,一味的使用 any 会导致我们失去 ts 带来的类型安全性优势。所以,在大多数情况下,我们应该优先考虑使用 unknown。当我们不确定一个值的类型,但又想保持类型安全性时,unknown 是一个很好的选择。

使用 unknown 可以强制我们在使用该值之前进行必要的类型检查,这有助于捕获潜在的类型错误,提高代码的健壮性。

总结

总的来说,any 和 unknown 都为 ts 中的类型系统提供了一定的灵活性,但 unknown 更加注重类型安全。在实际开发中,我们应该尽量减少 any 的使用,多考虑使用 unknown,以充分利用 ts 的类型检查功能,写出更安全、更可靠的代码。

下次再想用 any 时,先问问自己:这里真的没法确定类型吗?用 unknown 会不会更安全?毕竟写 ts 的乐趣,不就是在类型系统的钢丝上优雅地跳舞吗?

前端基础理论——02

1. 详细阐述 HTML 语义化在 SEO 重要性。 提升搜索引擎理解能力 语义化标签(如
❌