Write the Code. Change the World.

分类目录
5月 07

很多情况下,我们都想将 react 编译的项目部署到非跟目录。可默认就必须是根目录,怎么实现呢。

操作一波

找到 package.json 文件,仅仅在package.json 文件中加入一行 "homepage":".", 即可。 如下。

{
  …
  "private": true,
  "homepage":".",
  "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
     "axios": "^0.19.2",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
  …
}

网上还有一种方法,将配置文件暴露出来。执行 npm run eject ,然后修改配置。但感觉这方法比较麻烦,也没弄通过。

如果仅仅是想部署到非跟目录,第一种方法最省事了。

更有意义

https://www.jb51.net/article/158194.htm

https://segmentfault.com/a/1190000018593030

https://blog.csdn.net/zhaolandelong/article/details/78468842

https://www.cnblogs.com/lishanlei/p/9550622.html

4月 24

之所以总结是因为最近也是在看css方面,让我迷惑的是有很多文章 关于布局名词都没有听说过,工作中也很少用。但是我们要与时俱进,叫的多了,它就成为正式名词了。比如 ‘双飞翼 ‘圣杯’??并且我还发现我几乎看了好多文章,他们在从大的角度总结时候,几乎很少有相同,分类角度不尽相同。其实这也没关系,本来就没有一个统一标准,但是还是希望有个相对能够理解 概括角度看css布局。

我们从css发展到现在大类可以归纳为以下几种,你了解的实现方法可能就是以下方式中的具体实现:

  • 静态布局
  • 自适应布局
  • 流式布局(又别名 百分比布局 %)
  • 响应式布局:媒体查询
  • 弹性布局 (rem/em flex布局)
    继续阅读
4月 23

用 ant design pro, 就逃不开 umi,现在更是到 umi3 了。

Umi 2 发布已经是一年半之前的事了,在这段时间里,我们发现之前的架构正逐渐不能满足业务飞速发展的需要,于是我们重写了一遍 Umi。经过几个月的 “007 ” 研发,Umi 3 在今天正式和大家见面了,并调整 slogan 为“插件化的企业级前端应用框架”。

Umi 是什么?

有些朋友可能还不太了解 Umi。

Umi 是蚂蚁金服的底层前端框架,已直接或间接地服务了 3000+ 应用,包括 java、node、H5 无线、离线(Hybrid)应用、纯前端 assets 应用、CMS 应用等。他已经很好地服务了我们的内部用户,同时希望他也能服务好外部用户。

它包含以下特性:

  • 🎉 可扩展,完整的生命周期,插件化,支持插件和插件集
  • 📦 开箱即用,内置路由、构建、部署、测试等,仅需一个依赖即可上手
  • 🐠 企业级,经蚂蚁内部 3000+ 项目以及阿里、优酷、网易、飞猪、口碑等公司项目的验证
  • 🚀 大量自研,微前端、组件打包、文档工具、请求库、hooks 库、数据流等
  • 🌴 完备路由,支持配置式路由和约定式路由,同时保持功能的完备性
  • 🚄 面向未来,一直在尝试新技术的探索,dll 提速、modern mode、webpack@5、自动化化 external、bundler less 等
    继续阅读
4月 13

npm start 的时候,可能会遇到 Something is already running on port 8000. 这样的问题。要不关掉占用 8080 端口的进程,要不使用别的端口来运行 npm server 。

win 下关闭 8080 端口,可以直接使用任务管理器。建议还是用其他端口比较好,可能 8080 端口的进程刚好是自己需要的。

# 使用 8090 端口来启动进程
port=8090 npm start
4月 10

官网文档:https://pro.ant.design/docs/i18n-cn

使用

  1. 先配置。
可以配置在 src/locales/ 中,也可以配置在各自的组件目录中。
比如,新建了一个模块 TestForm, locales 就可以放在这里边。个人觉得,各个模块的 locales 放在各自的目录下表好。包括 model service 这些。

# locales/zh-CN.ts
export default {
    'login.formlogin.login': '登录',
    'login.formlogin.register': '注册',
    'login.formlogin.account': '账号',
    'login.formlogin.account-placeholder': '请输入账号',
}  
# locales/zh-TW.ts
export default {
    'login.formlogin.login': '登錄',
    'login.formlogin.register': '註冊',
    'login.formlogin.account': '賬號',
    'login.formlogin.account-placeholder': '请输入賬號',
}
  1. 再使用。
# 使用 FormattedMessage 或 使用 formatMessage 

import { FormattedMessage, formatMessage } from "umi";

<Form.Item label={<FormattedMessage 
id="login.formlogin.account" />} 
rules={[{ required: true, message: formatMessage({id:'login.formlogin.account-placeholder'}) }]}>
    <Input placeholder={formatMessage({id:'login.formlogin.account-placeholde'})}/>
</Form.Item>

不过,使用 formatMessage 的时候,会报下边的警告。

Warning: Using this API will cause automatic refresh when switching languages, please use useIntl or injectIntl.

使用此 api 会造成切换语言的时候无法自动刷新,请使用 useIntl 或 injectIntl。

可是对 placeholder 如果不使用 formatMessage 又不行。真为难。

下边一个简单的 login 的列子

不喜欢官方的登录注册页,不仅繁琐,还不好看。

index.tsx

import { connect } from 'umi';
import { ConnectState } from '@/models/connect';

import FormLogin from './FormLogin';

const Login = () => {
    const onLoginSubmit = (value:any) => {
        console.log(value);
    }

    return (
        <FormLogin onLoginSubmit={onLoginSubmit} ></FormLogin>
    );
};


export default connect(({ login, loading }: ConnectState) => ({
    userLogin: login,
    submitting: loading.effects['login/login'],
}))(Login);

locales/zh-CN.ts

export default {
    'login.formlogin.login': '登录',
    'login.formlogin.register': '注册',
    'login.formlogin.account': '账号',
    'login.formlogin.account-placeholder': '请输入账号',
    'login.formlogin.account-rule': '请输入正确的账号格式(手机号码)',
    'login.formlogin.password': '密码',
    'login.formlogin.password-placeholder': '请输入密码',
    'login.formlogin.password-rule': '密码不能为空',
    'login.formlogin.remember-password': '记住密码',
    'login.formlogin.forget-password': '忘记密码',
    'login.formlogin.none-account': '如果没有账号,请',
}

FormLogin/index.tsx

import React from "react";
import styles from "./index.less";
import { FormattedMessage, formatMessage } from "umi";
import { Card, Form, Input, Button, Checkbox } from "antd";

const itemLayout = {
  labelCol: {
    xs: { span: 24 },
    sm: { span: 6 },
  },
  wrapperCol: {
    xs: { span: 24 },
    sm: { span: 12 },
  },
};

const tailLayout = {
  wrapperCol: {
    xs: { span: 24 },
    sm: { offset:6, span: 12 }
  },
};

const NormalLoginForm = (props:any) => {
  return (
    <Card bordered={false} title={<FormattedMessage id='login.formlogin.login' />} style={{ width: 720 }}>
      <Form
        {...itemLayout}
        initialValues={{ remember: true }}
        onFinish={props.onLoginSubmit}
      >
        <Form.Item name="account" label={<FormattedMessage id='login.formlogin.account' />} rules={[{ required: true, pattern: /^1[3456789]\d{9}$/ , message: <FormattedMessage id='login.formlogin.account-rule' /> }]}>
          <Input placeholder={ formatMessage({ id: 'login.formlogin.account-placeholder' })} />
        </Form.Item>

        <Form.Item name="password" label={<FormattedMessage id="login.formlogin.password" />} rules={[{ required: true, message: <FormattedMessage id='login.formlogin.password-rule' /> }]}>
          <Input.Password placeholder={formatMessage({ id: 'login.formlogin.password-placeholder' })} />
        </Form.Item>

        <Form.Item {...tailLayout} name="remember" valuePropName="checked">
          <Checkbox>
            <FormattedMessage id="login.formlogin.remember-password" />
          </Checkbox>
        </Form.Item>

        <Form.Item {...tailLayout}>
          <Button type="primary" htmlType="submit">
            <FormattedMessage id='login.formlogin.login' />
          </Button>

          <Button type="link" href="https://baidu.com" target="_blank">
            <FormattedMessage id='login.formlogin.forget-password' />
          </Button>
        </Form.Item>

        <Form.Item {...tailLayout} className="other-way">
            <span><FormattedMessage id='login.formlogin.none-account' /></span>
            <Button type="link" href="https://baidu.com" size="small">
              <FormattedMessage id='login.formlogin.register' />
            </Button>
        </Form.Item>
      </Form>
    </Card>
  );
};

export default (props:any) => (
  <div className={styles.container}>
    <div id="form-login">
      <NormalLoginForm onLoginSubmit={props.onLoginSubmit} />
    </div>
  </div>
);

FormLogin/index.less

.container {
  height: 100%;

  :global {
    #form-login {
      display: flex;
      flex-direction: row;
      justify-content: center;
      align-items: center;
      width: 100%;
      height: 100%;
      background-color: rgba(255,0,0,0.1);

      .login-form {
        width: 100%;
        max-width: 720px;
      }
    }

    .other-way {
      margin-top: 20px;
      margin-bottom: 0;
    }
  }
}

到目前为止,service,model 都是空的。要实现登录注册的功能,就得完善它们。

1月 11

为了早点体验到 ant design pro 的页面,注释了部分配置,显示出了 welcome 页面。可是当你刷新页面时候,到你面前的就是 404 。这里,就要先解决 404 问题。

Ant Design Pro 使用的 Umi 支持两种路由方式:browserHistoryhashHistory。默认就是 browserHistory。这样漂亮好看。而默认就是这种方式。具体请看:

https://pro.ant.design/docs/deploy-cn#%E5%89%8D%E7%AB%AF%E8%B7%AF%E7%94%B1%E4%B8%8E%E6%9C%8D%E5%8A%A1%E7%AB%AF%E7%9A%84%E7%BB%93%E5%90%88

虽然这种方式漂亮好看。可对于解析认识是个问题。需要服务端进行处理。因为只使用过 nginx,这里就说下 nginx 下的解决方法。

找到对应的 nginx 的配置文件,并修改文件。

如果 ant design pro 编译后的文件直接在根目录下,可以在配置文件中增加

    location /  {
        index index.html index.htm;
        try_files $uri $uri/ /index.html;
    }

如果 ant design pro 编译后的文件不再根目录下(比如在 dashboard 下),可以在配置文件中增加

    location /dashboard  {
        index index.html index.htm;
        try_files $uri $uri/ /dashboard/index.html;
    }

然后保存重启 nginx ,再刷新页面,发现不再是 404 了。

nginx -s reload

homestead 中,虚拟机里的配置文件在 /etc/nginx/sites-available/ 下,可以单独修改,重启。但这样重启后,会再生。清理的脚步在本地的 Homesteadscripts/clear-nginx.sh 中。如果单独对某个域名处理,最好在本地的 scripts/site-types/ 下新建配置,然后 Homestead.yml 文件中,对具体域名指定配置。

多的话就不说了,具体还是 nginx 的修改配置。 至于 nginx 的 try_files 是什么意思了,可以了解下。

try_files 的核心目的就是替代。比如,

location /dashboard  {
    index index.html index.htm;
    try_files $uri $uri/ /index.php?q=$uri&$args;
}

以 根目录(对应站点的根目录)下的 dashboard 目录为参照,先查找第一个节点,看文件是否存在,如果不存在再看文件夹(有斜杠)是否存在,如果再不存在,就指向到第三个参数。

比如具体的 url 是这样的。

https://www.mlxiu.com/dashboard/love

它会先检查 love 这个文件是否存在,如果不存在 love 这个文件则检查 love 这个文件夹。当然,文件的后缀,上边已先列出。

具体看 nginx 的 try_files

https://blog.csdn.net/a519640026/article/details/9138329

https://www.cnblogs.com/boundless-sky/p/9459775.html

1月 03

socket.io 默认支持断开重连。可经常断开长连也很不好。短时间内(比如一分钟)断开重连这种现象,很容易观察到,并且可以观察到时间都很准时,每经过某个时间就会断开然后重连。

遇到这个现象,该怎么处理呢。如果熟悉 socket.io 的机制,可能想到心跳问题。这里先跳过。

遇到这个现象 ,可能你会找服务端的原因。是 nginx 出问题了,还是实现 socket.io 的服务出现问题了。(socket.io 是 websocket 的包装版)

  1. 观察 nginx,因为 nginx 的代码最少。发现 nginx timeout 都注释掉了。
# proxy_connect_timeout 60s;
# proxy_send_timeout 60s;
# proxy_read_timeout: Nginx will close the connection if the proxied server does not send data to Nginx in 60 seconds; At the same time, this close behavior is also affected by heartbeat setting of Swoole.
# proxy_read_timeout 60s;
  1. 分析框架源码,这个是最漫长,和可能性大的。毕竟用到别人的框架。这里使用 swoole + laravels + websocket 的 socket.io 的封装。找了好久也没找到问题。

  2. 找不到具体原因,然后去看 socket.io 的文档。才发现,心跳没发送是一个原因。

https://socket.io/docs/server-api/#new-Server-httpServer-options

重述具体现象

前端页面连接好 socket.io 后,如果前端不发送任何消息,60 秒内,服务端就会断开服务。然后重连。如果在 60 秒之内,你有发送消息,这个断开时间就会延长。直到你不发消息后的 60 秒。那如果这样,利用 socket.io 的心跳不就解决了。可是,从始至终,都没看见过发送过心跳。原来是服务端设置的心跳时间太长了,远远超过了 60 秒。也就是心跳还没来得及发,服务端就已经断开了,前端就已经又重连了。既然这样,那控制心跳时间小于 60 秒不就好了。

既然是一种方法,那么前端 socket.io 怎么发送这个心跳呢。

前端连接好服务端后,服务端会推送一个消息下来,这个消息包裹以下信息:

0{"sid":"NWUwZWY4NzBiYjk1OQ==","upgrades":[],"pingInterval":44000,"pingTimeout":36000}

就是以 pingInterval 这个频率来发送心跳。 pingInterval 要大于 pingTimeout。 这样就可以很直接的看到心跳的间隔。修改服务端代码,onOpen 的时候,推送这个消息过来就好,并 pingInterval 是正确的值。

心跳就是 ping pong。一来一回,建立起来就好。

末了

socket.io 是 websocket 的封装版,就是在 websocket 上加入了约定好的算法以及计算,来实现 socket.io 的这种连接方式。

上边虽然解决了不再一分钟断开又重连。可是这个心跳频率还是有点高。因为上边提到的一分钟就断开,导致没办法把心跳设置大一点。这个问题需要处理。还是得去了解源码,进一步分析吧。