布局那些属实没什么好说的嗷,直接写组件吧
当然你也可以选择用 antd
Button
超常用组件 Button(其实是原生的和 antd 的都太难用了)
思考作为 button,我们需要什么属性
显然有颜色定制、点击事件定制、样式定制、文本定制等 4 个主要需求
同时我自己有一个彩色和黑白转换的需求
那么容易得到以下类型定义
1 | interface ButtonProps { |
接下来写组件
1 | const Button = (props: ButtonProps) => { |
颜色我随便选的,看官请自便
先取除颜色外的值,并提供默认值,然后根据是否 plain
而取颜色,并设置不同的默认值
然后返回一个由 TouchableOpacity
构成的可点击组件,并注入样式和内容
最后写样式表
1 | .buttonWrapper { |
使用方法:
1 | import Button from './src/component/Button' |
此处选用 TouchableOpacity
,是因为我反复验证后,觉得这个表现最好
详情可以参考文档:https://reactnative.cn/docs/touchableopacity
Tabs
对于 Tabs
组件,我们显然需要标签名、内容和 onChange
事件
所以容易得到如下类型定义
1 | interface TabsProps { |
但是我们如何在用户变更选择的时候,切换渲染的内容呢?
所以我们容易得到一个 Tab
组件,该组件要求用户传入 name
属性,然后根据 name
来选择内容;同时要求 Tabs
组件只接受 Tab
作为直接子级
那么容易得到 Tab
组件代码
1 | import React from 'react'; |
进而可以得到 Tabs
组件代码
1 | const Tabs = (props: TabsProps) => { |
通过 useEffect
在初始化时检查直接子级是否符合要求
最后是样式表
1 | .tabs { |
Loading
要制作 Loading 动画,我们了解 rn 原生的动画机制
文档:https://reactnative.cn/docs/animated
我们首先要从 react-native
中引入 Animated
和 Easing
然后声明一个 value,用于在动画进程中作为变化值
类似于 css 原生动画中指示某个属性的变化,value 需要声明起点、终点和过度时间、过度函数
首先创建 value
1 | const value = useRef(new Animated.Value(0)).current; |
此处必须是通过 useRef
创建的一个引用,否则 value 就是常量,无法变化了
然后定义一个动画变化函数
1 | const startAnime = () => { |
该函数声明了动画的起点:setValue(0)
,以及动画的依赖
Animated.timing
用于创建一个动画,第一个参数是依赖的值,第二个参数是 options
,包括以下参数:
参数 | 含义 |
---|---|
toValue | 动画变化值的终点值 |
duration | 变化的时长,单位 ms |
easing | 动画的变化函数 |
useNativeDriver | 是否启用原生动画 |
如果使用原生动画,则 rn 会在动画开始前将动画信息发送到原生代码,从而使得动画在原生平台的
UI 线程上执行,js 就可以去执行其它任务了
如果不启用,则动画是 js 对每一帧的桥接
特别的,如果启用原生动画,则所有依赖相同动画值的动画都要启用原生动画
动画创建完成后,通过 start
方法启动动画,默认只执行一次。动画可以通过 stop
方法手动中断,并且其它中断也都是调用了 stop
方法
start
方法可以接受一个函数作为参数,该函数接受一个包含 finished
属性的对象作为参数,表示动画结束后要执行的动作
当动画正常结束时,finished
会被置为 true
,否则为 false
我们可以通过该函数来实现动画的无限循环
1 | const startAnime = () => { |
到现在,我们已经创建了一个动画的变化,现在要渲染到视图上
我选用 <Icon name="loading" size={100} />
作为动画的内容,并且让其旋转
旋转功能通过 css 的 transform rotate 来实现
则有如下代码
1 | <Animated.View |
首先,根据动画值来设定样式的代码,必须在 Animated.View
上才会生效
然后,transform
接受一个数组,每个数组单位为一个对象,该对象描述了 transform
要执行的动作
此处我通过 interpolate
方法,创建了一个映射,将 value
的值范围映射到一个带有单位的范围上,以满足 rotate
的参数要求
interpolate
方法会自动识别单位,所以尽管写,它自己会适配
那么,一个旋转等待的动画就完成了
Html
rn 不支持直接嵌入 hmtl 片段,那我们总要想个办法支持一下
react-native-render-html
推荐通过 react-native-render-html
来实现这个需求
1 | yarn add react-native-render-html |
然后引入该库的默认导出
1 | import RenderHtml from 'react-native-render-html'; |
该组件的使用方式如下
1 | <RenderHtml source={{html}} contentWidth={width} /> |
如果你不提供 contentWidth
属性,则会触发警告,所以一般是提供
此处使用的 width
一般推荐用如下方式取得
1 | import {useWindowDimensions} from 'react-native'; |
那么易得组件代码如下
1 | import React from 'react'; |
为了满足 /forum/[id]
的预览需求,易得样式表如下
1 | .html { |
唯一的缺点就是在 RenderHtml
内部引入 css 很麻烦,不过它已经是最好用的了,总不能用 WebView 吧
WebView
说到 WebView,顺便介绍一下 WebView 的实现
通过 react-native-webview
来实现这个需求
1 | yarn add react-native-webview |
然后引入该库的默认导出
1 | import WebView from 'react-native-webview'; |
该组件的使用方式如下
1 | <View style={{...styles.html, height}}> |
通过 injectedJavaScript
以在 WebView 的内容初始化完成时,向父组件提交事件,可以类比于 html 的 iframe
通过 onMessage
来接收参数,并处理
同时,WebView 默认是 0 高度的,所以必须在初始化完成后,获取内部高度,然后设置到父组件上
也可以大量插入片段,并通过 link
来引入外源 css
1 | <View style={{...styles.html, height}}> |
缺点是,当一个页面有大量 WebView 的时候,会重复引入很多次 link
内容,同时造成进程负担极重,无法正常卸载组件,导致应用崩溃闪退
所以我不建议用 WebView(
Editor
caho 是有 markdown 编辑需求的,所以需要一个 markdown Editor
其实就是缝合怪(?
容易得到需求
- 无权限禁用
- 标题
- 提交动作
- 默认值
- 最大长度限制
- 附加插入内容
那么可得类型定义
1 | interface EditorProps { |
因为标题不一定存在,所以提交动作的第一个值是正文,第二个才是标题
容易得到组件代码
1 | const Editor = (props: EditorProps) => { |
扩增功能是因为其实并不是所有用户都会写 markdown,而 markdown 在写错换行符的时候是很烦的,所以要方便普通用户
然后是 forbidden 功能,鉴于 rn 的所有组件都是 box-sizing: content-box
,所以要修改层级以使得阻止视图完整覆盖编辑器
所以可得样式表
1 | .editor { |
感谢阅读