Wang Xu

Novice Developer & Tech Enthusiast | 菜鸟开发者 & 技术爱好者

Actively learning. Sharing thoughts on technology, programming, and self-improvement.

努力学习中。分享一些技术、编程以及自我提升的思考。

Resume/简历GitHub

React Native 中 useState 的基础理解与用法记录

此下 TypeScript-JSX 代码皆是在 React Native 中进行使用,与 React(Web) 有出入,在此提醒。 一、useState 的基本使用 1 const [count, setCount] = useState(0) 定义一个 count 状态值,setCount 为 useState 传递的更新函数。 直接将 count 更新为传入的值。 搭配 Button 使用 1 <Button title="Count + 1" onPress={() => setCount(count + 1)} /> 这里使用了箭头函数,保证只有在点击时才触发 setCount 函数。 为什么需要使用箭头函数? 如果直接写成: 1 onPress={setCount(count + 1)} 那么在组件渲染阶段函数就会被立即执行,而不是在点击时执行。 二、关于渲染与 useState 的一些补充理解 React 在渲染时,并不是完全重新渲染整个界面,而是通过 Virtual DOM 进行比对,只对发生变化的部分进行更新(局部 fix)。 这一部分我目前还没有系统学习,只是在学习过程中了解到一些相关知识。同时也了解到: useState 和 useReducer 底层其实是同一套逻辑 useState 本质上是 useReducer 的一个特殊情况。 三、useState 底层实现的关键发现(源码/与 ai 的讨论) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 ### 核心发现 useState 本质上就是 useReducer 的特殊情况 三个关键阶段 #### 1. 初始挂载(mount) function mountState<S>(initialState): [S, Dispatch] { const hook = mountStateImpl(initialState) // 创建一个 hook 对象 const queue = hook.queue // 创建更新队列 const dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue) queue.dispatch = dispatch return [hook.memoizedState, dispatch] // 返回 [状态值, 更新函数] } #### 2. 更新阶段(update) function updateState<S>(initialState): [S, Dispatch] { return updateReducer(basicStateReducer, initialState) // 调用 useReducer! } #### 3. Dispatcher 映射 const HooksDispatcherOnUpdate: Dispatcher = { // ... useState: updateState, // ... } #### 这说明了什么? | 你写的代码 | 实际运行 | | ------------- | ------------------------------------- | | `useState(0)` | `useReducer(basicStateReducer, 0)` | | `setCount(1)` | `dispatch({ type: null, action: 1 })` | #### basicStateReducer 是什么? function basicStateReducer<S>(state: S, action: S | ((S) => S)): S { return typeof action === 'function' ? action(state) : action } 你可以这样理解: - `setCount(1)` → 直接返回 `1` - `setCount(c => c + 1)` → 执行函数并返回新值 #### 这里体现的设计思想 1. **统一性**:useState 和 useReducer 底层逻辑统一 2. **多态**:不同渲染阶段使用不同的 dispatcher(mount / update / rerender) 3. **不可变性**:每次更新都会创建新的状态值,而不是修改旧值 再深入就是 Fiber 调度算法,这部分相对复杂,目前 AI 也没有建议我继续深入,我也暂时没有选择继续在这里消耗时间。 ...

December 28, 2025 · 2 min · Wang Xu

2025 Q4 复盘:实习、成长与表达

好久没有坐在桌子前写写blog了,从十月初开始第一份实习,算是一种成长了,感觉认识了很多新的技术栈,接下来就打算继续深入学习客户端开发了,完成自己从小的梦想,做自己的app,通过学习技术解决自己的需求,同时上架app,看看是否能帮到大家,也算是一种价值体现吧。 有的时候不知道该写些什么,总是感觉没有内容可以写,但是每天想说的话又很多,时不时想想到底什么时候才能不天天为了以后的生计着急,以后能干什么,什么时候才能出人头地。 总是过的很着急,可是有时心中有时又会想到,成长的过程本身就是一个从量变到质变的过程,因为总是想一步就达到自己心中自己的样子。说实话,这也是我浮躁的原因。思来想去,沉下心来做,会耗费很多时间,而我又担心浪费时间,时刻再斟酌,做这件事情本身是不是有意义的,是不是不浪费时间,对我达到心中的自己是否有榜之,是否有价值。自从上次跟GPT聊了之后发现,其实自己的能力还是很弱,渴望一步就获得别人29-30岁的成就,这怎么可能。同时我也不再每天坐在电脑前敲代码7-9小时了,我试着跑步,练习英语,还有写写blog,练习自己的表达能力。我觉得只要我去做了,能改变我的,这些都是有意义的,之前的坐在那里7-9小时,对身体的伤害太大了,我觉得这太消耗人的心气了,慢慢的我就变为工具人了,我也反思自己,如果我只会敲代码,我还会干什么?所以这也是我发布在抖音和blog的原因。记录一下自己的成长,也同时练习自己的表达能力。 我个人认为表达能力这个东西我觉得真的很重要,在你讲给别人的时候,表达能力让你能表达的更清晰,别人也能更清晰的理解你的描述,你能更清晰的表达你的心中所想,别人也能更清晰的理解你的心中所想。提高沟通效率。如上,我不知道有多少人能看到这里,上卖弄的表述我觉得其实不是很好,我是想到什么说什么,这样的话就很难留住人,锻炼表达能力的原因还有一个就是为了能让我的文字,更能够渲染情绪吧?让我的想法能更好的表达出来,让同频的人驻足,至少,一眼看过去不是不想读。

December 3, 2025 · 1 min · Wang Xu

One-api:Gin 接口配置、身份认证与邮件发送实践

碎知识 包在被导入时,会默认执行 init 的函数,如果有多个,会按照字典序执行。 接口配置 通过路由组的实例,来进行一个路由端点的定义 比如 apiRouter.GET("/status", controller.GetStatus) 定义一个 /status 并且设置了一个激活函数,必须是 func(\*Context) 传入上下文(类似中间件) 函数内部通过 c.JSON(http.StatusOK, gin.H{} 返回状态码,以及 json 序列化数据 一个简单请求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package main import "github.com/gin-gonic/gin" func main() { server := gin.New() // 定义一个路由端点 GET 请求 server.GET("/", func(c *gin.Context) { // gin.H 来创建 JSON 响应数据 // 通过 c.JSON() 发送给客户端 c.JSON(200, gin.H{ "message": "Hello, Gin!", "status": "success", }) }) // 启动服务器 // server.Run() 默认监听 8080 端口 server.Run(":8080") } 身份认证中间件 authHelper 辅助函数 首先通过会话 session 来进行判断用户的基础身份 通过上下文获取请求会话实例,通过会话实例获得用户的基础信息 ...

October 19, 2025 · 9 min · Wang Xu

One-api:Gin 路由配置方法,Redis&内存分别实现的请求速率限制

Gin 框架:路由与中间件的学习 进入 Gin 的范围,初始化工作已完成,现在开始处理路由,路由触发业务层逻辑以及中间件的使用,还有认证等功能。 30k 的项目,七品功法,名不虚传。 通过使用自定义函数,传入参数 router *gin.Engine 进行路由的配置。这样不仅分层清晰,业务逻辑也更明确,便于区分。接口不必随地配置,使得路由更加规范、简洁和精美。 请求路径 客户端 -> 服务器 -> 中间件 -> 路由 -> 中间件 -> 业务层 -> 中间件 -> 返回客户端 Gin 路由基础 路由分组 从当前节点创建一个子集路由组。 以 /api 为例: router.Group("/api") 中的 router 是传入的 *gin.Engine 类型。它会返回一个 *gin.RouterGroup。接收后,还可以继续进行分组。 1 2 3 4 // 一级路由组 apiRouter := router.Group("/api") // 二级路由组 userRoute := apiRouter.Group("/user") 路由中间件 通过 *gin.RouterGroup 可以进行中间件的单独使用。下级路由组在继承上级中间件的同时,也可以独立使用自己的中间件。 1 2 3 4 5 6 7 apiRouter := router.Group("/api") // 为 apiRouter 使用 Gzip 中间件 apiRouter.Use(gzip.Gzip(gzip.DefaultCompression)) // 创建二级路由组 adminRoute := userRoute.Group("/admin") // 注意:这里将 userRoute 改为 adminRoute 以匹配代码逻辑 // 只有 /api/admin/ 下的路由才会使用此中间件 adminRoute.Use(middleware.AdminAuth()) 定义请求处理函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 路由组 apiRouter := router.Group("/api") // 中间件 - gzip 压缩减少网络传输 apiRouter.Use(gzip.Gzip(gzip.DefaultCompression)) // 中间件 - 限制接口请求速率 apiRouter.Use(middleware.GlobalAPIRateLimit()) { // 定义路由组下的接口 // 在 /api/status 路径下定义了一个 GET 请求 // 触发调用 controller.GetStatus apiRouter.GET("/status", controller.GetStatus) // 当然也可以使用中间件,随后再去调用业务函数 apiRouter.GET("/models", middleware.UserAuth(), controller.DashboardListModels) } 定义请求的路径响应函数时,其签名是: GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes 它接收一个路径和多个 gin.HandlerFunc。传入的顺序即是触发的顺序,可以是中间件,也可以是业务函数。 ...

September 23, 2025 · 7 min · Wang Xu

One-api:HTTP请求与代理,Client,Transport

碎知识 switch v := input.(type) 类型断言 判断 input 的类型,并且赋值给 v HTTP 请求基础 延迟加载与按需加载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 proxyURL, err := url.Parse(config.UserContentRequestProxy) if err != nil { logger.FatalLog(fmt.Sprintf("USER_CONTENT_REQUEST_PROXY set but invalid: %s", config.UserContentRequestProxy)) } // 配置 Transport (传输层) 加载 Proxy transport := &http.Transport{ Proxy: http.ProxyURL(proxyURL), } UserContentRequestHTTPClient = &http.Client{ Transport: transport, Timeout: time.Second * time.Duration(config.UserContentRequestTimeout), } 按需使用 Transport 并且可以一个 Client 挂载不同的 Transport。 这种设计是一种优化策略,实现了延迟加载或按需加载。 Go HTTP 客户端组件 (Ai) http.Client (项目经理/你) 职责:最高层级的API,负责接收你的任务指令(例如:client.Get("http://example.com"))。它知道如何协调各种资源去完成这个任务。 重要性:这是你直接打交道的接口。 类比:你这个项目经理,你不亲自去国外,但你负责给任务、设定截止时间、检查结果。 http.Transport (实际执行境外任务的团队/交通工具) 职责:http.Client 会把具体的“如何发送请求、如何建立连接、如何处理重定向”等技术细节委托给 http.Transport 来处理。它是真正干活的“底层工人”或“交通工具”。一个 Client 可以指定它要使用哪个 Transport。 重要性:所有的网络连接、TLS握手、代理配置等都发生在这里。 类比:你的项目经理(Client)会把你派给一个专门执行境外任务的团队(Transport)。这个团队决定了要乘坐什么飞机、走什么路线、住什么酒店等执行细节。如果你不指定,项目经理会给你一个默认的团队 (http.DefaultTransport)。 http.Transport.Proxy (去国外的具体路线规划师/出入境管理局) 职责:这是 Transport 的一个字段,它决定了你的请求在发送到最终目的地之前,是否需要经过一个中间服务器(代理)。这个字段期望的是一个函数,这个函数能够根据当前的请求 (*http.Request),返回应该使用的代理服务器 (*url.URL)。 重要性:这里的灵活性很高,你可以根据不同的请求动态选择不同的代理,甚至不使用代理。但最常见的情况是“所有请求都走同一个代理”。 类比:你的团队(Transport)在出发前,会有一个专门的顾问(Proxy 字段)来决定是否要办理签证,是否需要从某个特定口岸出境,或者是否需要经过某个中转站才能到达目的地。这个顾问知道所有“特殊路线”(代理)的规则。 代理配置辅助函数 url.Parse(string) (解析地址/查地图) 职责:把一个字符串形式的URL(比如 "http://myproxy.com:8080")解析成 Go 语言内部可以方便操作的 *url.URL 结构体。 重要性:这是将人类可读的字符串转化为程序可操作数据的第一步。 类比:你要去一个地方,首先要在地图上查到它的精确坐标,而不是只知道一个模糊的地名。 http.ProxyURL(*url.URL) (设定一个中转站) 职责:这是一个 helper function (http 包提供的工具函数)。它接收一个 *url.URL (你的代理服务器地址),并返回一个适合 http.Transport.Proxy 字段的函数。这个返回的函数很简单:无论收到什么请求,它都固定返回你传入的那个代理地址。 重要性:简化了最常见的“所有请求都通过同一个代理”的配置方式。你不需要手写一个匿名函数来返回固定的代理URL。 类比:你的团队(Transport)决定了要通过一个固定的中转站才能到达目的地。http.ProxyURL 就是来方便地告诉你的“路线规划师”(Proxy 字段),这个中转站(proxyURL)就是它要一直使用的。 HTTP 客户端配置示例 Init() 函数配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 func Init() { // 如果启用配置了代理地址 if config.UserContentRequestProxy != "" { logger.SysLog(fmt.Sprintf("using %s as proxy to fetch user content", config.UserContentRequestProxy)) // 解析string,转化为内部 *url.URL proxyURL, err := url.Parse(config.UserContentRequestProxy) if err != nil { logger.FatalLog(fmt.Sprintf("USER_CONTENT_REQUEST_PROXY set but invalid: %s", config.UserContentRequestProxy)) } // 配置 Transport (传输层) 加载 Proxy transport := &http.Transport{ Proxy: http.ProxyURL(proxyURL), } // 应用层 使用 传输层 组件 Transport UserContentRequestHTTPClient = &http.Client{ // 传输层组件 Transport: transport, // 超时时间 Timeout: time.Second * time.Duration(config.UserContentRequestTimeout), } } else { UserContentRequestHTTPClient = &http.Client{} } // 创建一个 http.RoundTripper 接口 var transport http.RoundTripper if config.RelayProxy != "" { logger.SysLog(fmt.Sprintf("using %s as api relay proxy", config.RelayProxy)) proxyURL, err := url.Parse(config.RelayProxy) if err != nil { logger.FatalLog(fmt.Sprintf("USER_CONTENT_REQUEST_PROXY set but invalid: %s", config.UserContentRequestProxy)) } // 为什么? #接口:`http.RoundTripper` transport = &http.Transport{ Proxy: http.ProxyURL(proxyURL), } } if config.RelayTimeout == 0 { HTTPClient = &http.Client{ Transport: transport, } } else { HTTPClient = &http.Client{ Timeout: time.Duration(config.RelayTimeout) * time.Second, Transport: transport, } } ImpatientHTTPClient = &http.Client{ Timeout: 5 * time.Second, Transport: transport, } } 多个 Client 实例的使用 声明三个全局变量,它们的类型都是 *http.Client 不同的链接使用不同的 Client 使用 http.RoundTripper 类型变量复用 ...

September 21, 2025 · 3 min · Wang Xu