巧妙编码,而非困难编码

2025-06-04

巧妙编码,而非困难编码

为从事大型生产应用程序的开发人员提供的一系列想法。

平均应用程序结构

为了尽可能广泛地覆盖受众,我将使用一个相当常见的设置进行演示。我们的普通应用程序……

  • 有一个静态登陆页面,上面有一些营销宣传。
  • 有一些公共页面,至少有一个登录和一个注册。
  • 拥有少量私人页面。
  • 使用 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
PREV
Docker、Kubernetes 和 Podman 在系统设计面试中有何区别?
NEXT
我如何使用自动化工具构建 300 多个开源项目