效果展示图
使用插件
-
Formik
负责表单校验、监听表单提交、数据校验错误信息展示
-
Yup
负责表单校验规则
分析页面
从上述的展示图我们可以看到的主要元素有:输入框、单选按钮和按钮。其中生成的密码长度不可能很大也不可能为负数和 0,所以我们可以限定密码长度输入框的规则,即密码长度最小 4 位,最大 16 位,所以我们需要进行表单数据校验操作。
因为我们生产的密码包含大小写、数字和特殊字符,所以我们需要有辅助的功能函数来帮我们来支撑业务。而密码生产的业务功能函数可以划分这几个部分:
-
生成密码字符串
存放大小写、数字和特殊字符变量,并且判断用户是否勾选了对应的生成条件,例如是否勾选了是否包含小写字母,并且调用创建密码的功能函数
-
创建密码
通过用户制定的规则生成对应的密码并返回
-
重置密码状态
重置密码生成器中所有数据的状态
构建页面
根据页面分析和页面展示,我们可以首先实现页面的整体搭建和样式名称的定义,具体代码如下:
export default function PasswordCheck() {
return (
<ScrollView keyboardShouldPersistTaps="handled">
<SafeAreaView style={styles.appContainer}>
<View style={styles.formContainer}>
<Text style={styles.title}>密码生产器</Text>
<View style={styles.inputWrapper}>
<View style={styles.inputColumn}>
<Text style={styles.heading}>密码长度</Text>
</View>
<TextInput
style={styles.inputStyle}
placeholder="Ex. 8"
keyboardType="numeric"
/>
</View>
<View style={styles.inputWrapper}>
<Text style={styles.heading}>是否包含小写字母</Text>
<BouncyCheckbox
disableBuiltInState
isChecked={lowerCase}
fillColor="#29AB87"
/>
</View>
<View style={styles.inputWrapper}>
<Text style={styles.heading}>是否包括大写字母</Text>
<BouncyCheckbox
disableBuiltInState
isChecked={upperCase}
fillColor="#FED85D"
/>
</View>
<View style={styles.inputWrapper}>
<Text style={styles.heading}>是否包括数字</Text>
<BouncyCheckbox
disableBuiltInState
isChecked={numbers}
onPress={() => setNumbers(!numbers)}
fillColor="#C9A0DC"
/>
</View>
<View style={styles.inputWrapper}>
<Text style={styles.heading}>是否包含符号</Text>
<BouncyCheckbox
disableBuiltInState
isChecked={symbols}
fillColor="#FC80A5"
/>
</View>
<View style={styles.formActions}>
<TouchableOpacity style={styles.primaryBtn}>
<Text style={styles.primaryBtnTxt}>生成密码</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.secondaryBtn}>
<Text style={styles.secondaryBtnTxt}>重置</Text>
</TouchableOpacity>
</View>
</View>
{isPassGenerated ? (
<View style={[styles.card, styles.cardElevated]}>
<Text style={styles.subTitle}>生成结果:</Text>
<Text style={styles.description}>长按密码进行复制</Text>
<Text selectable={true} style={styles.generatedPassword}>
{password}
</Text>
</View>
) : null}
</SafeAreaView>
</ScrollView>
);
}
编写样式
定义好框架后,我们也有对应的样式名称,那么我们就可以逐步实现样式。
const styles = StyleSheet.create({
appContainer: {
flex: 1,
},
formContainer: {
margin: 8,
padding: 8,
},
title: {
fontSize: 32,
fontWeight: "600",
marginBottom: 15,
},
subTitle: {
fontSize: 26,
fontWeight: "600",
marginBottom: 2,
},
description: {
color: "#758283",
marginBottom: 8,
},
heading: {
fontSize: 15,
},
inputWrapper: {
marginBottom: 15,
alignItems: "center",
justifyContent: "space-between",
flexDirection: "row",
},
inputColumn: {
flexDirection: "column",
},
inputStyle: {
padding: 8,
width: "30%",
borderWidth: 1,
borderRadius: 4,
borderColor: "#16213e",
},
errorText: {
fontSize: 12,
color: "#ff0d10",
},
formActions: {
flexDirection: "row",
justifyContent: "center",
},
primaryBtn: {
width: 120,
padding: 10,
borderRadius: 8,
marginHorizontal: 8,
backgroundColor: "#5DA3FA",
},
primaryBtnTxt: {
color: "#fff",
textAlign: "center",
fontWeight: "700",
},
secondaryBtn: {
width: 120,
padding: 10,
borderRadius: 8,
marginHorizontal: 8,
backgroundColor: "#CAD5E2",
},
secondaryBtnTxt: {
textAlign: "center",
},
card: {
padding: 12,
borderRadius: 6,
marginHorizontal: 12,
},
cardElevated: {
backgroundColor: "#ffffff",
elevation: 1,
shadowOffset: {
width: 1,
height: 1,
},
shadowColor: "#333",
shadowOpacity: 0.2,
shadowRadius: 2,
},
generatedPassword: {
fontSize: 22,
textAlign: "center",
marginBottom: 12,
color: "#000",
},
});
编写对应功能函数
我们完成了页面的布局,接下来就是实现生产密码的功能,我这里拆解成生成密码字符串
、创建密码
和重置密码状态
三个功能函数,具体的功能函数如下:
/**
* 生成密码字符串
* @param passwordLength 密码长度
*/
const generatePasswordString = (passwordLength: number) => {
let characterList = ''; // 生产密码的所有相关字符
const upperCaseChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const lowerCaseChars = 'abcdefghijklmnopqrstuvwxyz';
const digitChars = '0123456789';
const specialChars = '!@#$%^&*()_+';
// 根据用户的选择,把相关字符拼接到characterList中
if (upperCase) {
characterList += upperCaseChars
}
if (lowerCase) {
characterList += lowerCaseChars
}
if (numbers) {
characterList += digitChars
}
if (symbols) {
characterList += specialChars
}
const passwordResult = createPassword(characterList, passwordLength)
setPassword(passwordResult)
setIsPassGenerated(true)
}
/**
* 根据密码总字符串和密码长度生产随机字符串
*
* @param characters 生产密码的所有相关字符
* @param passwordLength 密码长度
* @returns 生成的随机密码
*/
const createPassword = (characters: string, passwordLength: number) => {
let result = ''
for (let i = 0; i < passwordLength; i++) {
const characterIndex = Math.round(Math.random() * characters.length)
result += characters.charAt(characterIndex)
}
return result
}
/**
* 密码重置
*/
const resetPasswordState = () => {
setPassword('')
setIsPassGenerated(false)
setLowerCase(true)
setUpperCase(false)
setNumbers(false)
setSymbols(false)
}
表单校验
在简单介绍 React Native 整合 Formik 实现表单校验中我只是简单介绍了Formik
的常用的几个属性,而这次我们要使用如下几个属性:
属性 | 类型 | 说明 |
---|---|---|
touched | { [field: string]: boolean } | 判断表单字符是否已经访问或者修改过 |
isValid | boolean | 如果没有错误(即错误对象为空),则返回 true,否则返回 false |
handleChange | (e: React.ChangeEvent) => void | 主键更新 values[key]对应的值,其中 key 是事件发出输入的名称属性。如果 name 属性不存在,handleChange 将查找输入的 id 属性 |
具体的代码如下:
<Formik
initialValues={{ passwordLength: "" }}
validationSchema={PasswordSchema}
onSubmit={(values) => {
generatePasswordString(+values.passwordLength);
}}
>
{({
values,
errors,
touched,
isValid,
handleChange,
handleSubmit,
handleReset,
}) => (
<>
<View style={styles.inputWrapper}>
<View style={styles.inputColumn}>
<Text style={styles.heading}></Text>
{touched.passwordLength && errors.passwordLength && (
<Text style={styles.errorText}>{errors.passwordLength}</Text>
)}
</View>
<TextInput
style={styles.inputStyle}
value={values.passwordLength}
onChangeText={handleChange("passwordLength")}
placeholder="例如:8"
keyboardType="numeric"
/>
</View>
<View style={styles.inputWrapper}>
<Text style={styles.heading}>是否包含小写字母</Text>
<BouncyCheckbox
disableBuiltInState
isChecked={lowerCase}
onPress={() => setLowerCase(!lowerCase)}
fillColor="#29AB87"
/>
</View>
<View style={styles.inputWrapper}>
<Text style={styles.heading}>是否包括大写字母</Text>
<BouncyCheckbox
disableBuiltInState
isChecked={upperCase}
onPress={() => setUpperCase(!upperCase)}
fillColor="#FED85D"
/>
</View>
<View style={styles.inputWrapper}>
<Text style={styles.heading}>是否包括数字</Text>
<BouncyCheckbox
disableBuiltInState
isChecked={numbers}
onPress={() => setNumbers(!numbers)}
fillColor="#C9A0DC"
/>
</View>
<View style={styles.inputWrapper}>
<Text style={styles.heading}>是否包含符号</Text>
<BouncyCheckbox
disableBuiltInState
isChecked={symbols}
onPress={() => setSymbols(!symbols)}
fillColor="#FC80A5"
/>
</View>
<View style={styles.formActions}>
<TouchableOpacity
disabled={!isValid}
style={styles.primaryBtn}
onPress={() => handleSubmit()}
>
<Text style={styles.primaryBtnTxt}>生成密码</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.secondaryBtn}
onPress={() => {
handleReset();
resetPasswordState();
}}
>
<Text style={styles.secondaryBtnTxt}>重置</Text>
</TouchableOpacity>
</View>
</>
)}
</Formik>