调试
调试工具
Pipel-React 提供了多种调试工具来帮助你理解和调试响应式数据流。
debug 操作符
debug 操作符可以在控制台打印流的值变化:
tsx
import { usePipel, debug } from 'pipel-react'
function Example() {
const [count, count$] = usePipel(0)
// 添加调试
count$
.pipe(
debug('count') // 标签名称
)
.subscribe()
return <button onClick={() => count$.next(count + 1)}>增加</button>
}
// 控制台输出:
// [count] 0
// [count] 1
// [count] 2使用 then 进行调试
你可以使用 then 方法在数据流中执行副作用:
tsx
import { map } from 'pipeljs'
const [data, data$] = usePipel({ count: 0 })
// 使用 then 打印值
data$.then((value) => {
console.log('Data changed:', value)
})
// 或者在 pipe 链中使用
const doubled$ = data$.pipe(map((v) => v.count * 2))
doubled$.then((value) => {
console.log('Doubled value:', value)
})React DevTools
组件名称
为组件添加有意义的名称,方便在 DevTools 中识别:
tsx
function UserProfile() {
const [user, user$] = usePipel({ name: 'John', age: 25 })
return <div>{user.name}</div>
}
// 在 DevTools 中显示为 "UserProfile"使用 displayName
tsx
const MemoizedComponent = memo(function UserCard({ user }) {
return <div>{user.name}</div>
})
MemoizedComponent.displayName = 'UserCard'性能调试
检测不必要的渲染
tsx
import { useEffect, useRef } from 'react'
function useRenderCount(componentName) {
const renderCount = useRef(0)
useEffect(() => {
renderCount.current += 1
console.log(`${componentName} rendered ${renderCount.current} times`)
})
return renderCount.current
}
function Example() {
const renderCount = useRenderCount('Example')
const [count, count$] = usePipel(0)
return (
<div>
<p>Render count: {renderCount}</p>
<p>Count: {count}</p>
<button onClick={() => count$.next(count + 1)}>增加</button>
</div>
)
}使用 React Profiler
tsx
import { Profiler } from 'react'
function onRenderCallback(
id, // 组件的 "id"
phase, // "mount" 或 "update"
actualDuration, // 本次更新花费的时间
baseDuration, // 不使用 memoization 的情况下渲染整棵子树需要的时间
startTime, // 本次更新开始渲染的时间
commitTime, // 本次更新提交的时间
interactions // 本次更新的 interactions 集合
) {
console.log({
id,
phase,
actualDuration,
baseDuration,
})
}
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<Example />
</Profiler>
)
}数据流调试
追踪数据变化
tsx
import { usePipel } from 'pipel-react'
import { useEffect } from 'react'
function Example() {
const [data, data$] = usePipel({ count: 0, name: '' })
// 追踪所有变化
useEffect(() => {
const child = data$.then((value) => {
console.log('Data changed:', value)
console.log('Stack trace:', new Error().stack)
})
return () => child.unsubscribe()
}, [data$])
return (
<div>
<button onClick={() => data$.set((d) => d.count++)}>增加计数</button>
</div>
)
}检测值变化
tsx
import { change } from 'pipeljs'
const [data, data$] = usePipel({ count: 0 })
// 使用 change 操作符检测变化
const changed$ = data$.pipe(change())
changed$.then((value) => {
console.log('Value changed:', value)
})
// 更新值
data$.next({ count: 0 }) // 触发
data$.next({ count: 1 }) // 触发常见问题调试
1. 组件不更新
问题: 修改了数据但组件没有重新渲染
检查清单:
tsx
// ❌ 错误:直接修改
const [user, user$] = usePipel({ name: 'John' })
user.name = 'Jane' // 不会触发更新
// ✅ 正确:使用 set() 或 next()
user$.set((u) => {
u.name = 'Jane'
})
user$.next({ ...user, name: 'Jane' })调试方法:
tsx
function Example() {
const [user, user$] = usePipel({ name: 'John' })
useEffect(() => {
console.log('Component mounted')
const subscription = user$.subscribe((value) => {
console.log('User changed:', value)
})
return () => {
console.log('Component unmounted')
subscription.unsubscribe()
}
}, [user$])
return <div>{user.name}</div>
}2. 内存泄漏
问题: 组件卸载后订阅没有清理
检查清单:
tsx
// ❌ 错误:忘记清理订阅
useEffect(() => {
stream$.subscribe((value) => {
console.log(value)
})
// 缺少清理函数
}, [])
// ✅ 正确:清理订阅
useEffect(() => {
const subscription = stream$.subscribe((value) => {
console.log(value)
})
return () => subscription.unsubscribe()
}, [stream$])调试方法:
tsx
// 追踪订阅数量
function useSubscriptionCount(stream$) {
useEffect(() => {
console.log('Subscriptions:', stream$.observers?.length || 0)
})
}3. 闭包陷阱
问题: 在回调中使用了过时的值
tsx
// ❌ 问题:使用闭包中的旧值
const [count, count$] = usePipel(0)
setTimeout(() => {
count$.next(count + 1) // count 可能已经过时
}, 1000)
// ✅ 解决:使用函数式更新
setTimeout(() => {
count$.set((c) => c + 1) // 总是使用最新值
}, 1000)调试方法:
tsx
function Example() {
const [count, count$] = usePipel(0)
const handleClick = () => {
console.log('Current count in closure:', count)
console.log('Current count in stream:', count$.value)
setTimeout(() => {
console.log('Count after 1s (closure):', count)
console.log('Count after 1s (stream):', count$.value)
}, 1000)
}
return <button onClick={handleClick}>Check</button>
}4. 异步竞态条件
问题: 多个异步请求的响应顺序不确定
tsx
// ❌ 问题:可能显示旧的搜索结果
const [query, query$] = usePipel('')
const [results, results$] = usePipel([])
query$.pipe(debounce(300)).subscribe(async (q) => {
const data = await fetch(`/api/search?q=${q}`)
results$.next(data) // 可能是旧请求的结果
})
// ✅ 解决:使用 switchMap
import { switchMap } from 'pipeljs'
const results = useObservable(
query$.pipe(
debounce(300),
switchMap((q) => fetch(`/api/search?q=${q}`).then((r) => r.json()))
)
)调试技巧
1. 使用 console.trace()
tsx
data$
.pipe(
tap(() => {
console.trace('Data updated from:')
})
)
.subscribe()2. 条件断点
tsx
data$
.pipe(
tap((value) => {
if (value.count > 10) {
debugger // 只在 count > 10 时暂停
}
})
)
.subscribe()3. 性能标记
tsx
import { map } from 'pipeljs'
const startTime = Date.now()
const mapped$ = data$.pipe(
map((value) => {
const duration = Date.now() - startTime
console.log('Update took:', duration, 'ms')
return value
})
)
mapped$.then((value) => {
console.log('Final value:', value)
})4. 自定义调试 Hook
tsx
function useDebugValue(value, label = 'Value') {
useEffect(() => {
console.log(`[${label}] changed:`, value)
}, [value, label])
return value
}
function Example() {
const [count, count$] = usePipel(0)
useDebugValue(count, 'Count')
return <div>{count}</div>
}生产环境调试
1. 条件调试
tsx
const DEBUG = process.env.NODE_ENV === 'development'
if (DEBUG) {
stream$.pipe(debug('stream')).subscribe()
}2. 错误边界
tsx
import { Component } from 'react'
class ErrorBoundary extends Component {
state = { hasError: false, error: null }
static getDerivedStateFromError(error) {
return { hasError: true, error }
}
componentDidCatch(error, errorInfo) {
console.error('Error caught:', error, errorInfo)
// 发送到错误追踪服务
}
render() {
if (this.state.hasError) {
return <div>Something went wrong</div>
}
return this.props.children
}
}
function App() {
return (
<ErrorBoundary>
<Example />
</ErrorBoundary>
)
}3. 日志收集
tsx
const logger = {
log: (message, data) => {
console.log(message, data)
// 发送到日志服务
if (process.env.NODE_ENV === 'production') {
// sendToLogService(message, data)
}
},
}
stream$.then((value) => {
logger.log('Stream updated', value)
})最佳实践
- 开发时启用调试 - 使用
then方法追踪数据流 - 使用 React DevTools - 检查组件树和 props
- 添加有意义的标签 - 为 Stream 和组件添加描述性名称
- 记录关键操作 - 使用
then记录重要的数据变化 - 性能分析 - 使用 Profiler 找出性能瓶颈
- 错误处理 - 使用 ErrorBoundary 捕获错误
- 条件调试 - 只在开发环境启用详细日志