公司之前一版的手机应用没有做业务、控制分离的处理,导致其他项目参考时,很难复用其中的功能。所以leader决定近期目标是封装一套公司内部用的基础组件和业务组件,目标是快速,试水。本篇记录一个花费时间较长的组件Radio。
作为一个主Java后端开发的人来说,设计一个前端的组件往往是参照常见的App。这次的比较简单,只需要找两个图标,见下图:
为了符合当前主题,改成蓝色即可。
src ├─assets // 资源 │ └─img // 图片 │ radio_button_select.png // 选中 │ radio_button_unselect.png // 非选中 ├─components //组件位置 │ └─radio │ PropsType.tsx // 组件属性 & 接口 │ Radio.tsx // 单个选项 │ RadioContext.tsx // 上下文 │ RadioGroup.tsx // 组
// Radio的接口
export interface IRadio {
/** 值 */
value: string;
/** 显示的文本 */
label: string;
/** 默认被选中 */
defalutChecked?: boolean;
/** 改变事件 */
onChange?: (value: boolean) => void;
}
前面三个都可以理解,最后一个是想到下面一种场景:
调查问卷中,根据不同选项,会有后续不同的问题。此时用来触发其他联动事件。
根据草图,想象得到是图片 + 文本的结构。再用可点击的控件包裹,于是得到如下
<Pressable
onPress={() => {
const changedValue = !checked;
setChecked(() => changedValue);
context?.onChange(value);
}}>
<View style={styles.container}>
<Image
style={styles.img}
source={checked ? selectImgPath : unSelectImgPath}
/>
<Text style={styles.text}>{label}</Text>
</View>
</Pressable>
补充说明:
TouchableHighlight
,但官方文档中说,未来以Pressable
为中心,所以就没再用。该控件是为了模拟点击时候的高亮,并且支持了更细颗粒度的触控效果,见下图:setChecked(() => changedValue);
需要在内部定义:
const [checked, setChecked] = useState(false);
是用来让画面上的显示和内部属性双向绑定,useState
内部的值为初始值,可以是很多类型,甚至函数。
使用格式也比较固定,假设xxx为属性名,是布尔(boolean)类型,那么得到如下:注意大小写
const [xxx, setXxx] = useState(false)
想要修改值的话,就用第setXxx
。
Q: 这里的值为什么用箭头函数
() => {}
再包裹一下? A: 有时候需要调用完set方法后,直接拿到修改后的值再去做其他修改。 比如你点击之后,想log一下看看真实的值,会发现一直保留上次的结果,与实际不同步。 这时候需要考虑使用这种方式了。主要问题来自React的渲染机制。
// 引入的控件
import {Image, Pressable, StyleSheet, Text, View} from 'react-native';
。。。省略
// 图片路径
const selectImgPath = require('../../assets/img/radio_button_select.png');
const unSelectImgPath = require('../../assets/img/radio_button_unselect.png');
。。。省略
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
img: {
height: 20,
width: 20,
},
text: {
fontSize: 16,
marginLeft: 10,
},
});
用普通的View
包裹即可,再加上一些样式。
甚至定义都可以写成:
// ViewProps来自原生组件View的接口
const RadioGroup = (props: ViewProps) => {...
到这里,画面就结束了。
但其实控件是“死”的,目前还没有控制住“单选”这一功能,而且外面(父组件)也拿不到我选了什么。
这时候一个Hooks的作用就出现了!那就是useContext
。
import * as React from 'react';
// 接口定义
interface IRadioGroupContext {
/** 当前选中的值 */
currentValue: string;
/** 更改事件 */
onChange: (value: string) => void;
}
// 创建上下文
const RadioGroupContext = React.createContext<IRadioGroupContext | null>(
null,
);
// 导出&起别名
export const RadioGroupContextProvider = THSRadioGroupContext.Provider;
// 导出
export default RadioGroupContext;
RadioGroup中的使用:
const onChange = (targetValue: string) => {
setCurrentValue(() => targetValue);
};
return (
<View style={styles.container}>
<RadioGroupContextProvider
value={{
currentValue: currentValue,
onChange: onChange,
}}>
{children}
</RadioGroupContextProvider>
</View>
);
将需要使用上下文的组件包裹起来,然后所有的值放在value中,这样就成了全局共享的变量、方法。
Radio中的第5行就是为了调用父组件的方法。另外补充:
// 得到上下文
const context = useContext(RadioGroupContext);
useEffect(() => {
setChecked(() => context?.currentValue === value);
}, [context?.currentValue, value]);
useEffect
是组件初始化和再次渲染都会执行的方法,第二个参数是调用了外部的变量就会触发更新。
使用的是useRef
,主要分两步骤
const RadioGroup = forwardRef((props: IRadioGroup, ref: RefRadio) => {...});
useImperativeHandle(ref, () => {
currentValue;
});
外面要想取得,就可以这样
const radioRef = React.useRef<any>(null);
。。。
console.log(ref.?current?.currentValue)
。。。
<RadioGroup ref={radioRef}>。。。
正确来说,要引入StoryBook库来展示。可是时间,能力有限,就采用Excle的方式了。
格式是组件名,图例,使用,接口属性。
以上就是一个简单的Radio组件开发流程了。
作为一个后端同学,对于React的开发还是比较好上手的,只是有些时候会比较难理解一些函数钩子(Hooks)。比如踩过无数次坑的useMemo
,以至于现在都不怎么考虑使用了。
还有一点需要注意的就是做好规范,搭建项目的时候,把eslint的配置统一。包括eslint react的插件,能帮助我们更安全高效的使用和学习React Native。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。