0. 引言
刚开始学习 React,做了一个简单的 Demo 实现微信一键登录(手机端通过微信客户端登录)。
因为微信接口必须写在后台(appid, secret 不能暴露到客户端),所以用 Express 简单搭了一个服务端,页面跳转用 react-router,状态管理使用 v16.8 推出的 Hooks API(useReducer 和 useContext)。
因为后端服务器跨域的问题,开发环境按照官方的推荐使用 http-proxy-middleware。生产部署的时候需要额外配置一下 nginx,静态资源和后端请求分开提供服务。
虽然是个小 Demo,但是写起来东西还是挺多的,于是分开几篇逐步介绍几块内容。本文主要是关于项目初始化和登录状态的管理与跳转。
[Demo 的完整代码在 这里]
1. 初始化项目
直接使用官方工具 create-react-app 创建项目,然后安装以下依赖包:
- react-router-dom
- node-sass
- axios
- express
- http-proxy-middleware
为了方便组件的导入,更改 package.json 文件的 scripts 代码块,增加 NODE_PATH=src 变量如下:
"scripts": {
"start": "NODE_PATH=src react-scripts start",
"build": "NODE_PATH=src react-scripts build",
"test": "NODE_PATH=src react-scripts test",
"eject": "react-scripts eject"
},
这样就可以简化以后的导入命令:
import LoginPage from '../../pages/Login'
// 简化为 --->
import LoginPage from 'pages/Login'
项目目录结构如下:
project
|-- src
|-- pages
|-- AppLayout.js
|-- Login.js
|-- NoWechat.js
|-- components
|-- route
|-- AuthorizedRoute.js
|-- images
|-- utils
|-- stylesheets
| -- pages
|-- _appLayout.scss
|-- _login.scss
| -- components
| -- variables
| -- application.scss
|-- App.js
|-- context-manager.js
|-- setupProxy.js
|
|-- app_server.js
我习惯于集中管理样式文件,所以把所有样式统一放到 stylesheets 文件夹下,不过文章主要记录功能实现,就不罗列样式代码了,可自行前往源代码查看(很简单的例子而已)。
接下来看具体实现步骤,分别介绍以上文件的内容及作用。
2. 状态管理
使用 useReducer Hook 来记录用户的登录状态,并用 useContext Hook 向内部组件穿透。
状态管理本身抽到 src/context-manager.js 文件中,任何状态的扩展都在这个文件中进行。先来看一下它的内容:
import React from 'react'
export const AppContext = React.createContext()
export const initState = {
isLogin: false,
}
export const reducer = (state, action) => {
switch(action.type) {
case "LOGIN_SUCCESS":
return {
isLogin: true,
}
default:
return state
}
}
然后只需导入该文件,通过 <AppContext.Provider value=> 将状态穿透到所有内部组件即可。
于是改写 App.js 如下:
import React, { useReducer } from 'react'
import { BrowserRouter as Router } from 'react-router-dom'
import 'stylesheets/application.scss'
import AppLayout from 'pages/AppLayout'
import { AppContext, reducer, initState } from 'context-manager' // 状态步骤穿透 1
function App() {
const [state, dispatch] = useReducer(reducer, initState) // 状态步骤穿透 2
return (
<AppContext.Provider value=> // 状态步骤穿透 3
<Router>
<AppLayout />
</Router>
</AppContext.Provider>
)
}
export default App;
任何内部组件要拿到 state 只需像这样申明:
import { AppContext } from 'context-manager.js'
const { state } = useContext(AppContext)
后面会看到具体用法。
3. 登录跳转
这个 Demo 的主要功能是用户登录,它的前端界面的核心功能是根据用户身份(状态)显示不同页面,合法则显示受保护页面,不合法则显示登录页。
在这个登录跳转的功能上我参照了 react-router 的官方例子,用一个自定义路由组件(用于验证)把受保护页面路由包裹起来。
最外层是 AppLayout 组件,包裹了所有的页面:
import React from 'react';
import { Route, Switch } from 'react-router-dom'
import AuthorizedRoute from 'route/AuthorizedRoute'
import LoginPage from 'pages/Login'
const AppLayout = () => {
return (
<Switch>
<Route path="/login">
<LoginPage />
</Route>
<AuthorizedRoute path="/">
<Route exact path="/">
<h1>This is Home Page.</h1>
</Route>
<Route path="/about">
<h1>This is About Page.</h1>
</Route>
</AuthorizedRoute>
</Switch>
)
}
export default AppLayout
然后是关键的 AuthorizedRoute 组件:
import React, { useContext } from 'react';
import { Route, Redirect } from 'react-router-dom'
import { AppContext } from 'context-manager.js' // 状态引用步骤 1
const AuthorizedRoute = ({ children, ...rest }) => {
const { state } = useContext(AppContext) // 状态引用步骤 2
return (
<Route
{...rest}
render={({ location }) =>
state.isLogin ? ( // 通过 isLogin 状态判断,显示访问页面 or 显示登录页面
children
) : (
<Redirect
to={
{
pathname: '/login',
state: { from: location } // 将访问页面 location 传给 login,这样登录后就能跳转
}
}
/>
)
}
/>
)
}
export default AuthorizedRoute
最后是 Login 组件:
import React, { useContext } from 'react'
import { Redirect, useLocation } from 'react-router-dom'
import { AppContext } from 'context-manager.js'
const LoginPage = () => {
const { state, dispatch } = useContext(AppContext)
const location = useLocation()
const { from } = location.state || { from: { pathname: '/' }}
if(state.isLogin) {
return (
<Redirect to={from.pathname} /> // 如果直接访问登录页,则跳转到 '/'
)
} else {
return (
<button onClick={() => dispatch({type: "LOGIN_SUCCESS"})}>
微信一键登录
</button>
)
}
}
export default LoginPage
4. 总结
这里简单地实现了登录功能,点击『微信一键登录』按钮触发在 context-manager.js 中定义好的 dispatch 方法,更新状态 isLogin 为 true,随即触发 Login 组件的 render,然后 Redirect 到访问页面,登录成功。
比如,访问 / 和 /about 都会自动跳转到 /login 页面,点击『微信一键登录』按钮后,就会跳转到 / 或 /about 受保护页面。
下一步就是改写登录的业务逻辑,实现微信登录。但是,如引言所说,微信登录接口不能放在客户端调用,我们不能做一个纯前端应用然后让用户在微信端直接一键登录,因为这样会暴露敏感数据。所以在实现微信登录之前,下一篇会先介绍如何搭建一个最简化的 Express 后台。