From a0c4b177f891d33dbbd285ac5265fecac60054cf Mon Sep 17 00:00:00 2001 From: zhaoyang Date: Tue, 21 Nov 2023 18:58:26 +0800 Subject: [PATCH] =?UTF-8?q?update=20doc=E3=80=8A=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E5=B0=81=E8=A3=85-=E7=BD=91=E7=BB=9C?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E7=BB=84=E4=BB=B6=E3=80=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- content/posts/rn/base-components/BaseImage.js | 274 ++++++++++++++++++ content/posts/rn/base-components/index.md | 83 +++++- public/index.html | 6 - public/index.xml | 12 - public/posts/index.html | 20 -- public/posts/index.xml | 12 - public/sitemap.xml | 3 - 7 files changed, 351 insertions(+), 59 deletions(-) create mode 100644 content/posts/rn/base-components/BaseImage.js diff --git a/content/posts/rn/base-components/BaseImage.js b/content/posts/rn/base-components/BaseImage.js new file mode 100644 index 0000000..d100f13 --- /dev/null +++ b/content/posts/rn/base-components/BaseImage.js @@ -0,0 +1,274 @@ +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { Image, View } from "react-native"; +import FastImage from "react-native-fast-image"; + +// aliyun oss image format +// error image url: https://cdn.xxx.com/pic/illustrationstory/default/default_cover_720-1080.png?x-oss-process=image/resize,w_150/quality,q_85/format,webp +// normal image url: https://cdn.xxx.com/pic/illustrationstory/custom/scene/202310/2721/1698415166233-0yZkuCBu9I_960-960.png?x-oss-process=image/resize,w_150/quality,q_85/format,webp + +/** + * 图片组件 + * @param {require('xxx') | {uri: string}} errorSource 加载失败的图片 + */ +export default React.memo((props) => { + const { + source, + style, + resizeMode, + onLoad, + errorSource, + // todo: 加载中的图片 + // pendingSource, // loading状态的图片、加载失败的图片 + // pendingStyle, // loading状态的图片样式 + } = props; + + const { loadStatus, onLoadStart, onLoadEnd, onLoadError } = useWebImage({ + source, + }); + // console.log("props.source: ", source?.uri); + // console.log("loadStatus:", loadStatus); + + if (!source) { + return null; + } + + const _style = amendStyle(style); + + const isWebImage = useMemo(() => source?.uri?.startsWith("http"), [source]); + console.log("isWebImage:", isWebImage); + + // const renderPending = () => { + // return pendingSource ? ( + // + // + // + // ) : ( + // + // ); + // }; + + console.log("_style:", _style); + const renderError = () => { + console.log("renderError:", source?.uri, _style); + + let { width, height } = getWH(_style); + + console.log("width height", width, height); + + let w = width; // 340 / 3 + let h = height; // 260 / 3; + if (!width || !height) { + //兜底一个宽高 + w = 340 / 3; + h = 260 / 3; + } + + return ( + + + + ); + }; + + return ( + { + //console.log("onLoad::", source?.uri); + onLoad?.(e); + }} + onLoadStart={onLoadStart} + onLoadEnd={onLoadEnd} + onError={onLoadError} + source={source} + onLayout={(e) => { + //console.log("onLayout:", e.nativeEvent.layout); + }} + > + {props.children} + {/* {isWebImage && loadStatus == kLoadStatus.PENDING && renderPending()} */} + {isWebImage && loadStatus == kLoadStatus.ERROR && renderError()} + + ); +}); + +const kLoadStatus = { + IDLE: "IDLE", + PENDING: "PENDING", + SUCCESS: "SUCCESS", + ERROR: "ERROR", +}; + +function useWebImage({ source }) { + const uri = source?.uri; + + const [loadStatus, setLoadStatus] = useState(kLoadStatus.IDLE); + const isLoadEndRef = useRef(false); //用于解决onLoadError回调后,居然还会走onLoadEnd回调的问题 (判断error走了 end方法就不处理state了) + + useEffect(() => { + console.log("setLoadStatus(kLoadStatus.IDLE):", uri); + setLoadStatus(kLoadStatus.IDLE); + isLoadEndRef.current = false; + }, [uri]); + + const onLoadStart = () => { + // console.log("onLoadStart~"); + setLoadStatus(kLoadStatus.PENDING); + }; + + const onLoadEnd = (e) => { + // console.log("onLoadEnd~ loadStatus", e, loadStatus); + !isLoadEndRef.current && setLoadStatus(kLoadStatus.SUCCESS); + isLoadEndRef.current = true; + }; + + const onLoadError = () => { + // console.log("onLoadError~"); + setLoadStatus(kLoadStatus.ERROR); + isLoadEndRef.current = true; + }; + + return { + loadStatus, + onLoadStart, + onLoadEnd, + onLoadError, + }; +} + +// 对style修复 (未设置宽高的,设置兜底宽高20) +const amendStyle = (style) => { + //对于style未给宽高的,甚至style都undefined,设置兜底宽高20 + const extraStyle = { minWidth: 20, minHeight: 20 }; + + let _style; + const { width, height } = getWH(_style); + if (!width || !height) { + _style = Array.isArray(style) + ? [...style, extraStyle] + : [style, extraStyle]; + } + if (!_style) { + _style = style; + } + + return _style; +}; + +// 从style获取宽高 +const getWH = (style) => { + let w = 0; + let h = 0; + if (Array.isArray(style)) { + // 从后往前遍历,取最后一个有宽高的对象 + for (let index = style.length - 1; index >= 0; index--) { + let obj = style[index]; + if (!w && obj?.width) { + w = obj.width; + } + if (!h && obj?.height) { + h = obj.height; + } + if (w && h) { + break; + } + } + } else { + let obj = style; + if (obj?.width) { + w = obj.width; + } + if (obj?.height) { + h = obj.height; + } + } + return { width: w, height: h }; +}; + + +/* +使用示例 +```javascript +import HBImage from "app/components/image/HBImage"; +import { getSize, parseSize } from "app/utils/image/measurer"; + + +const FeedItem = (props) => { + + const coverUrl = useMemo( + () => getImageUrl(item.coverUrl, imageWidth), //oss resize 拼接url + [item.coverUrl] + ); + + const imageHeightFromUrl = useMemo(() => { + return getHeightFromSize(parseSize(coverUrl)); // parseSize从url(..._720-1080.png...)中解析出宽高 【正常100%都能取出】 + }, [coverUrl]); + + const [imageHeight, setImageHeight] = useState(imageHeightFromUrl); + + useEffect(() => { + if (!(imageHeightFromUrl > 0)) { + //若从url中未能解析出宽高,就通过RN官方Image.getSize方式获取宽高 【正常100%不可能走这里头】 + let h = imageSize; + getSize(coverUrl) + .then((size) => { + if (size) { + h = getHeightFromSize(size); + } + setImageHeight(h); + kImageHeightCache[coverUrl] = h; + }) + .catch((err) => { + setImageHeight(h); + }); + } else { + kImageHeightCache[coverUrl] = imageHeightFromUrl; + } + }, [coverUrl]) + + + return <> + {imageHeightFromUrl || imageHeight ? ( + 0 ? imageHeightFromUrl : imageHeight, + }, + ]} + source={{ uri: coverUrl }} + resizeMode={"contain"} + errorSource={require("app/components/image/default_cover_720-1080.png")} + /> + ) : null} + +} + +const getHeightFromSize = (size) => { + if (!(size?.width > 0) || !(size?.height > 0)) return 0; + return Math.min((imageSize * size.height) / size.width, maxImageHeight); +}; + +``` +*/ + diff --git a/content/posts/rn/base-components/index.md b/content/posts/rn/base-components/index.md index 430ae60..6d839ce 100644 --- a/content/posts/rn/base-components/index.md +++ b/content/posts/rn/base-components/index.md @@ -1,17 +1,16 @@ --- title: "封装常用基础组件" date: 2023-11-01T12:00:11+08:00 -draft: false +draft: true --- +# 瀑布流列表base组件 -* 瀑布流列表base组件 +源码:[BaseList.js](./BaseList.js) -源码[BaseList.js](./BaseList.js) +优点:将下拉刷新、上拉加载更多相关的 pageNo逻辑,封装到BaseList中,使用方仅需关注 数据请求的api,让使用方代码量降到最少。 -> 将下拉刷新、上拉加载更多相关的 pageNo逻辑,封装到BaseList中,使用方仅需关注 数据请求的api,让使用方代码量降到最少。 - -使用示例 +### 使用示例 ```javascript import BaseList from "app/components/list/base"; import { getFanList } from "./service"; @@ -46,3 +45,75 @@ const FanList = (props) => { ``` + +# 图片base组件 + +源码:[BaseImage.js](./BaseImage.js) + +优点: 使用了高性能图片库FastImage,封装为 可配置图片加载失败后展示的占位error图 + + +使用示例 +```javascript +// aliyun oss image format +// error image url: https://cdn.xxx.com/pic/illustrationstory/default/default_cover_720-1080.png?x-oss-process=image/resize,w_150/quality,q_85/format,webp +// normal image url: https://cdn.xxx.com/pic/illustrationstory/custom/scene/202310/2721/1698415166233-0yZkuCBu9I_960-960.png?x-oss-process=image/resize,w_150/quality,q_85/format,webp + +import HBImage from "app/components/image/HBImage"; +import { getSize, parseSize } from "app/utils/image/measurer"; + +const FeedItem = (props) => { + + const coverUrl = useMemo( + () => getImageUrl(item.coverUrl, imageWidth), //oss resize 拼接url + [item.coverUrl] + ); + + const imageHeightFromUrl = useMemo(() => { + return getHeightFromSize(parseSize(coverUrl)); // parseSize 从url(..._720-1080.png...)中解析出宽高 【正常100%都能取出】 + }, [coverUrl]); + + const [imageHeight, setImageHeight] = useState(imageHeightFromUrl); + + useEffect(() => { + if (!(imageHeightFromUrl > 0)) { + //若从url中未能解析出宽高,就通过RN官方Image.getSize方式获取宽高 【正常100%不可能走这里头】 + let h = imageSize; + getSize(coverUrl) + .then((size) => { + if (size) { + h = getHeightFromSize(size); + } + setImageHeight(h); + kImageHeightCache[coverUrl] = h; + }) + .catch((err) => { + setImageHeight(h); + }); + } else { + kImageHeightCache[coverUrl] = imageHeightFromUrl; + } + }, [coverUrl]) + + return <> + {imageHeightFromUrl || imageHeight ? ( + 0 ? imageHeightFromUrl : imageHeight, + }, + ]} + source={{ uri: coverUrl }} + resizeMode={"contain"} + errorSource={require("app/components/image/default_cover_720-1080.png")} + /> + ) : null} + +} + +const getHeightFromSize = (size) => { + if (!(size?.width > 0) || !(size?.height > 0)) return 0; + return Math.min((imageSize * size.height) / size.width, maxImageHeight); +}; +``` \ No newline at end of file diff --git a/public/index.html b/public/index.html index fbd8f55..729b832 100644 --- a/public/index.html +++ b/public/index.html @@ -219,12 +219,6 @@

More

-

- - 封装常用基础组件 - -

-

hugo + github pages usage diff --git a/public/index.xml b/public/index.xml index 9a80041..2d8ea3e 100644 --- a/public/index.xml +++ b/public/index.xml @@ -64,18 +64,6 @@ React Native 编码提效 on VSCode // AAView.js import { xxCalculate, xxUploadEvent } from &#39;app/components/XxModule&#39; import AView from &#39;./components/AView&#39; import BView from &#39;./components/BView&#39; //$$ 用于组件函数内的变量 紧邻写在组件函数上方 const variableUsedInXXComp = ... //组件函数 const AAView = props =&gt; { //$$ props解构, 放第一行 ///reason:即使AAView上面不写@param注释,通过这里的解构也能看出props的参数 const { paramA, paramB } = props //$$ 副作用函数(即use开头的函数),写在顶层作用域, const [aEverClicked, setAEverClicked] = useState(false) const [bClickedTime, setBClickedTime] = useState(0) . - - 封装常用基础组件 - https://zyestin.github.io/zyestin/posts/rn/base-components/ - Wed, 01 Nov 2023 12:00:11 +0800 - - https://zyestin.github.io/zyestin/posts/rn/base-components/ - 瀑布流列表base组件 源码BaseList.js -将下拉刷新、上拉加载更多相关的 pageNo逻辑,封装到BaseList中,使用方仅需关注 数据请求的api,让使用方代码量降到最少。 -使用示例 -import BaseList from &#34;app/components/list/base&#34;; import { getFanList } from &#34;./service&#34;; const pageSize = 10; const FanList = (props) =&gt; { const { userId } = props; const _requestFunc = (page, pageSize) =&gt; { return getFanList({ userId: userId, pageNo: page, pageSize }); }; const _renderItem = ({ item, index }) =&gt; { return &lt;Item {...item} /&gt;; }; return ( // &lt;View style={{ flex: 1 }}&gt; &lt;BaseList requestFunc={(page) =&gt; _requestFunc(page, pageSize)} contentContainerStyle={{ paddingVertical: px2dp(5), paddingLeft: px2dp(5), }} renderItem={_renderItem} // numColumns={1} uniqueKey={&#34;userId&#34;} /&gt; // &lt;/View&gt; ); }; - - hugo + github pages usage https://zyestin.github.io/zyestin/posts/hugo-usage/ diff --git a/public/posts/index.html b/public/posts/index.html index e6a8cec..ef0e178 100644 --- a/public/posts/index.html +++ b/public/posts/index.html @@ -172,26 +172,6 @@

- Posts -

- - 封装常用基础组件 - -

- -
-
- -
- -
-
-
Posts

diff --git a/public/posts/index.xml b/public/posts/index.xml index 39afcdb..127167e 100644 --- a/public/posts/index.xml +++ b/public/posts/index.xml @@ -64,18 +64,6 @@ React Native 编码提效 on VSCode // AAView.js import { xxCalculate, xxUploadEvent } from &#39;app/components/XxModule&#39; import AView from &#39;./components/AView&#39; import BView from &#39;./components/BView&#39; //$$ 用于组件函数内的变量 紧邻写在组件函数上方 const variableUsedInXXComp = ... //组件函数 const AAView = props =&gt; { //$$ props解构, 放第一行 ///reason:即使AAView上面不写@param注释,通过这里的解构也能看出props的参数 const { paramA, paramB } = props //$$ 副作用函数(即use开头的函数),写在顶层作用域, const [aEverClicked, setAEverClicked] = useState(false) const [bClickedTime, setBClickedTime] = useState(0) . - - 封装常用基础组件 - https://zyestin.github.io/zyestin/posts/rn/base-components/ - Wed, 01 Nov 2023 12:00:11 +0800 - - https://zyestin.github.io/zyestin/posts/rn/base-components/ - 瀑布流列表base组件 源码BaseList.js -将下拉刷新、上拉加载更多相关的 pageNo逻辑,封装到BaseList中,使用方仅需关注 数据请求的api,让使用方代码量降到最少。 -使用示例 -import BaseList from &#34;app/components/list/base&#34;; import { getFanList } from &#34;./service&#34;; const pageSize = 10; const FanList = (props) =&gt; { const { userId } = props; const _requestFunc = (page, pageSize) =&gt; { return getFanList({ userId: userId, pageNo: page, pageSize }); }; const _renderItem = ({ item, index }) =&gt; { return &lt;Item {...item} /&gt;; }; return ( // &lt;View style={{ flex: 1 }}&gt; &lt;BaseList requestFunc={(page) =&gt; _requestFunc(page, pageSize)} contentContainerStyle={{ paddingVertical: px2dp(5), paddingLeft: px2dp(5), }} renderItem={_renderItem} // numColumns={1} uniqueKey={&#34;userId&#34;} /&gt; // &lt;/View&gt; ); }; - - hugo + github pages usage https://zyestin.github.io/zyestin/posts/hugo-usage/ diff --git a/public/sitemap.xml b/public/sitemap.xml index d1c6e76..4f5154e 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -16,9 +16,6 @@ https://zyestin.github.io/zyestin/posts/rn/code-standards/ 2023-11-01T12:00:11+08:00 - - https://zyestin.github.io/zyestin/posts/rn/base-components/ - 2023-11-01T12:00:11+08:00 https://zyestin.github.io/zyestin/posts/hugo-usage/ 2023-08-24T09:11:47+08:00