fix: 修复 VideoBox 组件中 imageRef 的类型定义,避免潜在的空引用错误;优化 Sync 组件中的 itemWidth 计算,提升性能

This commit is contained in:
康猛 2026-01-20 15:36:28 +08:00
parent f4ef24b28f
commit 79a8dae49f
4 changed files with 22 additions and 28 deletions

View File

@ -16,7 +16,7 @@ type Props = {
// 默认宽度256半屏宽度
const VideoBox = ({ url, needWeb = true, width = 256, style, autoplay = true, ...videoProps }: Props) => {
const [urlFinal, setUrlFinal] = useState('')
const imageRef = useRef<ImageRef>(null)
const imageRef = useRef<ImageRef | null>(null)
const createUrl = (url: string) => {
return `https://modal-dev.bowong.cc/api/custom/video/converter/v2?media_url=${encodeURI(url)}&options=compression_level=3,quality=70,loop=true,resolution=${width}x${width},fps=24`

View File

@ -38,3 +38,7 @@ Use `bun` as the package manager (`package.json` `packageManager`).
## Configuration & Environment
- Environment files live in `.env.*` and are loaded with `dotenv` in scripts.
- Build profiles and OTA channels live in `eas.json`; do not commit secrets.
## rule
Always use Context7 MCP when I need library/API documentation, code generation, setup or configuration steps without me having to explicitly ask.

View File

@ -1,6 +1,6 @@
import { Ionicons } from '@expo/vector-icons'
import { useIsFocused } from '@react-navigation/native'
import { Block, ConfirmModal, Img, ListEmpty, Text, Toast, VideoBox } from '@share/components'
import { Block, ConfirmModal, Img, ListEmpty, Text, Toast } from '@share/components'
import { FlashList } from '@shopify/flash-list'
import { Image } from 'expo-image'
import { LinearGradient } from 'expo-linear-gradient'
@ -385,10 +385,10 @@ const Index = observer(function Index() {
if (first !== null && last !== null) {
// 每列3个所以前后各加3行9个item
for (let i = Math.max(0, first - 9); i < first; i++) {
for (let i = Math.max(0, first - 6); i < first; i++) {
if (allItems[i]) currentVisible.add(allItems[i].id)
}
for (let i = last + 1; i < Math.min(allItems.length, last + 10); i++) {
for (let i = last + 1; i < Math.min(allItems.length, last - 6); i++) {
currentVisible.add(allItems[i].id)
}
}
@ -447,11 +447,12 @@ const Index = observer(function Index() {
}
numColumns={3}
onEndReached={handleLoadMore}
// 设置合理的回收池大小,避免内存无限增长
maxItemsInRecyclePool={12}
// 设置合理的回收池大小,复用组件实例以提高性能
maxItemsInRecyclePool={0}
// 自动移除不可见的原生视图,停止渲染和解码
removeClippedSubviews={true}
// 减小预渲染距离,降低内存占用
drawDistance={150}
drawDistance={300}
onEndReachedThreshold={0.3}
refreshControl={<RefreshControl colors={['#FFE500']} refreshing={refreshing} onRefresh={handleRefresh} />}
renderItem={renderItem}
@ -461,7 +462,7 @@ const Index = observer(function Index() {
viewabilityConfig={{
itemVisiblePercentThreshold: 10,
minimumViewTime: 50,
waitForInteraction: false,
waitForInteraction: true,
}}
showsVerticalScrollIndicator={false}
data={allItems}
@ -583,26 +584,16 @@ const HeroCircle = observer<HeroCircleProps>(function HeroCircle({ selectedItem,
const { balance } = userBalanceStore
const Width = 216
const previewUrl = selectedItem?.webpHighPreviewUrl || selectedItem?.url || ''
const existItem = !!selectedItem?.url
const previewUrl = selectedItem?.webpHighPreviewUrl || selectedItem?.webpPreviewUrl || ''
return (
<Block className="relative z-10 items-center">
<Block className="relative mx-[12px] flex-row justify-between">
<Block className="flex-1">
<Block className="relative z-10 mt-[20px] flex size-[224px] rounded-full border-4 border-black shadow-deep-black">
{existItem && (
<Block className="relative size-full overflow-hidden rounded-full">
<VideoBox style={{ height: Width, width: Width, borderRadius: Width }} width={256} url={previewUrl} />
</Block>
)}
{!existItem && (
<Block className="relative size-full overflow-hidden rounded-full bg-transparent">
<Text className="text-[14px] font-bold text-white/60">...</Text>
</Block>
)}
<Block className="relative size-full overflow-hidden rounded-full">
<Img style={{ height: Width, width: Width, borderRadius: Width }} width={256} src={previewUrl} />
</Block>
<Block className="pointer-events-none absolute inset-0 rounded-full border-2 border-black/10" />

View File

@ -20,6 +20,8 @@ import { screenWidth, uploadFile } from '@/utils'
import { cn } from '@/utils/cn'
import { extractCdnKey } from '@/utils/getCDNKey'
const ITEM_WIDTH = Math.floor((screenWidth - 12 * 2 - 12 * 2) / 3)
// ============ 主组件 ============
const Sync = observer(() => {
// 从MobX Store获取用户信息
@ -53,8 +55,6 @@ const Sync = observer(() => {
const { connectedDevice, isScanning, transferProgress } = bleStore.state
const itemWidth = Math.floor((screenWidth - 12 * 2 - 12 * 2) / 3)
useFocusEffect(() => {
if (!isAuthenticated) {
router.replace('/')
@ -382,16 +382,15 @@ const Sync = observer(() => {
<GridItem
isSelected={isSelected}
isSelectionMode={isSelectionMode}
itemWidth={itemWidth}
itemWidth={ITEM_WIDTH}
post={post}
// 页面失焦时不渲染,减少内存占用
isVisible={isFocused}
onSelect={handleItemSelect}
key={post?.id}
/>
)
},
[isSelectionMode, selectedIds, selectedItem, itemWidth, handleItemSelect, isFocused],
[isSelectionMode, selectedIds, selectedItem, handleItemSelect, isFocused],
)
return (
@ -403,8 +402,8 @@ const Sync = observer(() => {
contentContainerStyle={{ paddingHorizontal: 12, paddingBottom: 200 }}
data={posts}
drawDistance={300}
maxItemsInRecyclePool={0}
removeClippedSubviews={true}
getItemType={() => 'row'}
ItemSeparatorComponent={() => <Block style={{ height: 6 }} />}
keyExtractor={(item: any) => item?.id || `fallback-${Math.random()}`}
ListFooterComponent={ListFooter}