今天我们接着上次的内容学习,本次学习的主要内容是React中路由的使用和创建导入账号界面。由于这次学习系列中全部使用React函数组件和Hook,所以之前使用类组件的读者需要切换过来。这里是React Hook的官方文档:=> https://react.docschina.org/docs/hooks-intro.html
React路由主要涉及到react-router
和react-router-dom
这两个库,第一个库是让你可以在代码中进行路由导航;第二个库是个和节点相关的路由库(从名字就可以看出来),它用来进行路由定义、导航、匹配等。我们可以看到react-router-dom
这个库已经有路由导航功能了,为什么还要使用react-router
呢?因为它们的使用范围不同,react-router-dom
主要用于节点静态导航,比如直接点击链接来跳转;而react-router
用于代码中导航,比如可以在回调函数中使用。下面我们简要的介绍一下react-router-dom
这个库。大家也可以直接去看它官方的文档,这里放出地址: => https://reacttraining.com/react-router/web/guides/quick-start
react-router-dom
中,元素主要分成三类:
、
和
。各有不同的使用场景,我们平常主要使用
,它使用通常的url来保存路由;而
使用哈希#
来保存路由;
将路由保存在内存中,适用于无浏览器环境。
和
。和switch case
的用法类似。
、
和
。平常使用
和
。 react-router-dom
使用时有几点注意事项:
\
放在后面,也可以使用exact
属性来指定精确匹配BrowserRouter
之后通常需要在服务器端作一些配置(一般是重定向),这个可以参考我的一篇文章:React路由与Apache配置,使用HashRouter
不需要服务器端配置,因它是要访问的同一个页面。好了,介绍完毕,让我们先安装它们。
npm install react-router
npm install react-router-dom
我们计划完成后的界面如下图:
从这里可以看到它和创建账号的界面很像,因此可以从src\views\CreateWallet.js
进行简单修改得来。下面有一个助记词导入和私钥导入的切换按钮,可以切换两种导入方式,点击取消可以退回到创建账号界面。同样,导入功能并没有实现,这个放在后面统一做。
新建src/views/ImportWallet.js
代码如下:
import React, {useState} from 'react';
import {makeStyles} from '@material-ui/core/styles';
import {useSimpleSnackbar} from 'contexts/SimpleSnackbar.jsx';
import TextField from '@material-ui/core/TextField';
import FormControl from '@material-ui/core/FormControl';
import HowToReg from '@material-ui/icons/HowToReg';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import Avatar from '@material-ui/core/Avatar';
import { withRouter } from "react-router";
import { Link } from "react-router-dom";
const minLength = 12;
const useStyles = makeStyles(theme => ({
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main
},
title: {
marginTop: theme.spacing(1),
fontSize: 20
},
form: {
width: '100%', // Fix IE 11 issue.
marginTop: theme.spacing(1),
textAlign: 'center'
},
submit: {
fontSize: 18,
width: "40%",
marginTop: theme.spacing(2)
},
import: {
margin: theme.spacing(2),
color:"#f44336",
fontSize: 18,
textDecoration: "none"
},
wallet: {
textAlign: "center",
fontSize: 18
},
container: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
margin: theme.spacing(3)
}
}));
function ImportWallet({history}) {
const classes = useStyles();
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [isPrivateKey,setIsPrivateKey] = useState(true)
const [key,setKey] = useState('')
const showSnackbar = useSimpleSnackbar()
const changeKeyType = e => {
e.preventDefault()
setIsPrivateKey(isPrivate => !isPrivate)
}
const updatePassword = e => {
let _password = e.target.value;
setPassword(_password)
};
const updateConfirmPassword = e => {
let _confirmPassword = e.target.value;
setConfirmPassword(_confirmPassword)
}
const updateKey = e => {
setKey(e.target.value)
};
const cancelImport = e => {
history.push('/')
};
const onSubmit = e => {
e.preventDefault()
if (password !== confirmPassword) {
return showSnackbar("前后两次密码不一致", "error");
}
if (password.length < minLength) {
return showSnackbar("密码至少12位", "error");
}
}
return(
<div className={classes.container}>
<Avatar className={classes.avatar}>
<HowToReg/>
</Avatar>
<Typography className={classes.title}>
{ isPrivateKey ? "请输入你的私钥": "请输入你的助记词" }
</Typography>
<form className={classes.form} onSubmit={onSubmit}>
<FormControl margin="normal" fullWidth>
<TextField id="key-password-input"
label={isPrivateKey ? "私钥" : "助记词"}
required
type="password"
value={key}
onChange={updateKey}/>
</FormControl>
<FormControl margin="normal" fullWidth>
<TextField id="standard-password-input"
label="设置密码"
required
type="password"
autoComplete="current-password"
value={password}
onChange={updatePassword}/>
</FormControl>
<FormControl margin="normal" fullWidth>
<TextField id="confirm-password-input"
label="请再次输入密码"
required
type="password"
autoComplete="current-password"
value={confirmPassword}
onChange={updateConfirmPassword}/>
</FormControl>
<Button type='submit' variant="contained" color="primary" className={classes.submit}>
导入
</Button>
</form>
<Button variant="contained" color="primary" onClick={cancelImport} className={classes.submit}>
取消
</Button>
<Link to="#" onClick={changeKeyType} className={classes.import}>
{ isPrivateKey ? "从助记词导入" : "从私钥导入"}
</Link>
</div>
)
}
export default withRouter(ImportWallet)
上面的代码有几点需要解释的地方:
上面的代码最后导出时使用了withRouter
,它让我们可以在函数组件中使用history
属性,从而在cancelImport
方法里进行路由导航。
请注意以下这段代码,这里的Link
其实并不是发挥路由导航功能。原本应该是一个Button
的,但是为了达到在桌面端鼠标悬停时显示手形效果,硬生生的改成了Link
,希望大家能改进一下。
<Link to="#" onClick={changeKeyType} className={classes.import}>
{ isPrivateKey ? "从助记词导入" : "从私钥导入"}
</Link>
changeKeyType
这个方法,它里面的state设置setIsPrivateKey
传入了一个函数,而不是具体的值。这个函数的参数就是需要改变的state的上一个值,它返回一个新值。可以查看上面提到的Hook文档来学习详细的用法。const changeKeyType = e => {
e.preventDefault()
setIsPrivateKey(isPrivate => !isPrivate)
}
修改src\views\CreateWallet.js
,增加对账号导入界面的导入和路由导航,修改后的代码如下:
import React, {useState} from 'react';
import {makeStyles} from '@material-ui/core/styles';
import Avatar from '@material-ui/core/Avatar';
import AddIcon from '@material-ui/icons/PersonAdd';
import Button from '@material-ui/core/Button';
import FormControl from '@material-ui/core/FormControl';
import Typography from '@material-ui/core/Typography';
import { Link } from "react-router-dom";
import {useSimpleSnackbar} from 'contexts/SimpleSnackbar.jsx';
import TextField from '@material-ui/core/TextField';
const minLength = 12;
const useStyles = makeStyles(theme => ({
avatar: {
margin: theme.spacing(1),
backgroundColor: theme.palette.secondary.main
},
title: {
marginTop: theme.spacing(1),
fontSize: 20
},
form: {
width: '100%', // Fix IE 11 issue.
marginTop: theme.spacing(1),
textAlign: 'center'
},
submit: {
fontSize: 20,
width: "50%",
marginTop: theme.spacing(5)
},
import: {
fontSize: 18,
textDecoration:"none",
color:"#f44336",
margin: theme.spacing(4),
},
wallet: {
textAlign: "center",
marginTop: theme.spacing(0.5),
fontSize: 18
},
container: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
margin: theme.spacing(3)
}
}));
function CreateWallet() {
const classes = useStyles();
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const showSnackbar = useSimpleSnackbar()
const updatePassword = e => {
let _password = e.target.value;
setPassword(_password)
};
const updateConfirmPassword = e => {
let _confirmPassword = e.target.value;
setConfirmPassword(_confirmPassword)
}
const onSubmit = e => {
e.preventDefault();
if (password !== confirmPassword) {
return showSnackbar("前后两次密码不一致", "error");
}
if (password.length < minLength) {
return showSnackbar("密码至少12位", "error");
}
}
return (<div className={classes.container}>
<Avatar className={classes.avatar}>
<AddIcon/>
</Avatar>
<Typography className={classes.title}>
创建一个新账号
</Typography>
<form className={classes.form} onSubmit={onSubmit}>
<FormControl margin="normal" fullWidth>
<TextField id="standard-password-input"
label="设置密码"
required
type="password"
autoComplete="current-password"
value={password}
onChange={updatePassword}/>
</FormControl>
<FormControl margin="normal" fullWidth>
<TextField id="confirm-password-input"
label="再次输入密码"
required
type="password"
autoComplete="current-password"
value={confirmPassword}
onChange={updateConfirmPassword}/>
</FormControl>
<Button type='submit' variant="contained" color="primary" className={classes.submit}>
创建
</Button>
</form>
<Link to="/import" className={classes.import}>导入已有账号</Link>
<Typography color='secondary' className={classes.wallet}>
KHWallet,简单安全易用的
</Typography>
<Typography color='secondary' className={classes.wallet}>
以太坊钱包
</Typography>
</div>)
}
export default CreateWallet
这里可以看到,我们使用了导入已有账号
这行代码来进行路由导航从而转到导入账号界面。
修改src/views/Main.js
,增加路由定义与路由匹配,修改完成后的代码如下:
import React,{lazy,Suspense} from 'react';
import Grid from '@material-ui/core/Grid';
import {makeStyles} from '@material-ui/core/styles';
import WalletBar from 'components/WalletBar';
import Paper from '@material-ui/core/Paper';
import { isMobile } from 'react-device-detect';
import { BrowserRouter as Router, Route, Switch} from "react-router-dom";
const ImportWallet = lazy(() => import('./ImportWallet'));
const CreateWallet = lazy(() => import('./CreateWallet'));
const useStyles = makeStyles(theme => ({
root: {
marginTop: theme.spacing(isMobile ? 8 :10),
display: "flex",
justifyContent: "center"
}
}));
function SwitchPage() {
return (
<Suspense fallback ='loading'>
<Switch>
<Route path="/import" component={ImportWallet}/>
<Route path="/" component={CreateWallet}/>
</Switch>
</Suspense >
)
}
export default function Main() {
const classes = useStyles();
return (<div className={classes.root}>
<Grid item xs={12} sm={12} md={3}>
<Paper style={{
height: 600,
mixHeight: 600
}}>
<Router >
<WalletBar/>
<SwitchPage />
</Router>
</Paper>
</Grid>
</div>)
}
注意到这行代码:const ImportWallet = lazy(() => import('./ImportWallet'));
,这里使用了一个延迟导入功能。就是需要用到导入账号界面时才去装载这个界面。使用延时功能时需要使用Suspense
来进行包装。因为两个界面的Header(头部)是一样的,所以我们只对下面的Body部分使用了路由匹配。
好了,所有的修改结束了。让我们npm start
运行起来,如果提示缺少相关模块,直接进行安装即可。
点击导入已有账号就可以进入导入账号界面,在该页面点击取消就回退到主界面了。当然你也可以直接点击浏览器的前进、后退功能键。
本次学习到此就结束了,下一章计划编写登录页面和实现钱包的保存。
恳请大家留言指正或者提出改进意见。
码云地址:=> https://gitee.com/TianCaoJiangLin/khwallet