巧妙编码,而非困难编码
为从事大型生产应用程序的开发人员提供的一系列想法。
平均应用程序结构
为了尽可能广泛地覆盖受众,我将使用一个相当常见的设置进行演示。我们的普通应用程序……
- 有一个静态登陆页面,上面有一些营销宣传。
- 有一些公共页面,至少有一个登录和一个注册。
- 拥有少量私人页面。
- 使用 JWT 令牌进行身份验证。
- 使用 redux、react-router 和 axios 在 React 中编写。
- 由 create-react-app 引导。
我在一家咨询公司工作,这种情况很常见。希望你也能把以下想法应用到你喜欢的技术栈中。
提示#1:拥有可靠的 API 层
API 应该处理所有与网络相关的事务。
-
避免重复 URL 和标头,而是使用基本 API 实例。
-
在此处理身份验证。确保将身份验证令牌添加到两者
localStorage
以及基本 API 实例中。 -
使用 API 拦截器实现通用的回退行为 - 例如全局加载指示器和错误通知。
import axios from 'axios'
import store from '../store'
import { startLoading, stopLoading, notify } from '../actions'
const JWT_TOKEN = 'JWT_TOKEN'
// have a base api instance to avoid repeating common config - like the base URL
// https://github.com/axios/axios#custom-instance-defaults
const api = axios.create({
baseURL: process.env.REACT_APP_API_URL,
timeout: process.env.REACT_APP_API_TIMEOUT
})
// add the Auth header to the base API instance once here to avoid repeated code
if (localStorage.getItem(JWT_TOKEN)) {
const token = localStorage.getItem(JWT_TOKEN)
api.defaults.headers.Authorization = `Bearer ${token}`
}
// keep networking logic - like handling headers and tokens - in the network layer
export function login (token) {
api.defaults.headers.Authorization = `Bearer ${token}`
localStorage.setItem(JWT_TOKEN, token)
}
export function logout () {
delete api.defaults.headers.Authorization
localStorage.removeItem(JWT_TOKEN)
}
// handle generic events - like loading and 500 type errors - in API interceptors
api.interceptors.request.use(config => {
// display a single subtle loader on the top of the page when there is networking in progress
// avoid multiple loaders, use placeholders or consistent updates instead
store.dispatch(startLoading())
return config
})
api.interceptors.response.use(
resp => {
store.dispatch(stopLoading())
return resp
},
err => {
store.dispatch(stopLoading())
// if you have no specific plan B for errors, let them be handled here with a notification
const { data, status } = err.response
if (500 < status) {
const message = data.message || 'Ooops, something bad happened.'
store.dispatch(notify({ message, color: 'danger' }))
}
throw err
}
)
export default api
提示#2:保持状态简单
由于 API 已经涵盖了加载和通用错误处理,因此您无需使用完整的异步操作。在大多数情况下,覆盖成功事件就足够了。
action.js
import articlesApi from '../api/articles'
const LIST_ARTICLES = 'LIST_ARTICLES'
export function listArticles () {
return async dispatch => {
// no need to handle LIST_ARTICLES_INIT and LIST_ARTICLES_ERROR here
const articles = await articlesApi.list()
dispatch({ type: LIST_ARTICLES, articles })
}
}
Reducer.js
import { LIST_ARTICLES } from '../actions/articles'
export function articles (state = [], { type, articles }) {
switch (type) {
case LIST_ARTICLES:
return articles
default:
return state
}
}
仅当您有特定的 B 计划时才应处理初始化和错误事件。
提示#3:保持路线简单
实现正确的ProtectedRoute
组件并非易事。建议为公共页面和受保护页面保留两棵独立的路由器树。登录和注销事件会自动在两棵树之间切换,并在必要时重定向到正确的页面。
import React from 'react'
import { Switch, Route, Redirect } from 'react-router-dom'
// isLoggedIn is coming from the redux store
export default App ({ isLoggedIn }) {
// render the private routes when the user is logged in
if (isLoggedIn) {
return (
<Switch>
<Route exact path="/home" component={HomePage} />
<Route exact path="/article/:id" component={ArticlePage} />
<Route exact path="/error" component={ErrorPage} />
<Redirect exact from="/" to="/home" />
<Route component={NotFoundPage} />
</Switch>
)
}
// render the public router when the user is not logged in
return (
<Switch>
<Route exact path="/login" component={LoginPage} />
<Route exact path="/register" component={RegisterPage} />
<Redirect to="/login" />
</Switch>
)
}
上述模式拥有良好的用户体验。它不会在登录和注销时添加历史记录,这正是用户所期望的。
提示#4:正确初始化应用程序
在确定用户是否登录或退出之前,请勿渲染任何内容。大胆猜测可能会导致公共/私人页面短暂闪烁,然后重定向到正确的页面。
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './store'
// do not render the app until we know if there is a logged in user or not
store.dispatch(getMe()).then(renderApp)
function renderApp () {
ReactDOM.render(<App />, document.getElementById('root'))
}
getMe()
应该调用/me
返回已登录用户或 401(未授权)错误代码的端点。检查 localStorage 中的 JWT 令牌是不够的,令牌可能已过期,这可能会导致用户无限重定向循环。
export function getMe (data) {
return async dispatch => {
try {
const user = await userApi.getMe(data)
dispatch({ type: LOGIN, user })
} catch (err) {
userApi.logout()
}
}
}
提示 5:使用登陆页面
回访用户可能已经对你的产品以及浏览器中缓存的应用感兴趣了。而新用户则不会,而且他们会很快做出判断。
服务端渲染整个应用可以给人留下良好的第一印象,但它却是目前最难的技术之一。先别急着上手。大多数情况下,你可以依靠一个简单的启发式方法:新手很可能会从你的落地页开始。
只需保持落地页简洁、静态,并与应用分离即可。然后在用户浏览落地页时,使用预加载或HTTP/2 推送来加载主应用。两者之间的选择取决于具体用例:如果只有一个较大的 bundle,则使用预加载;如果存在多个动态命名的小块,则使用 HTTP/2 推送。
我希望我能教你一些新的技巧!如果你读到这里,请分享这篇文章来帮助你。如果这篇文章得到足够的关注,我可能会再写一篇关于创建可复用组件的文章。
谢谢!
文章来源:https://dev.to/solkimicreb/code-smart-not-hard-bb1