简述

Preact,读:['pri:ækt],而非批React。跟Pure React也扯不上什么关系。

是由谷歌的一大兄弟开发和维护的优化版React。这并非造个轮子要取代FB的React,通过一些资料和实践,发现这个轮子并不简单。具体可以链接到官网,中文说明足够友好,作者真是太赞了。

官方:Preact官网
项目地址:Preact

实践笔记,第一部分

这也并非是近期的新鲜事物,只是我个人刚接触罢了。从这一个月的使用看来,这个确实比React简单一些。比如核心库直接去掉了PropTypes这种东西,对Render也进行了优化,搭配TypeScriptLess使用,爽的么法…

一. 这里记录下开发环境的配置,主要依赖情况:

  • Preact: ^8.2.6
  • typescript: ^3.2.4
  • Less: ^3.9.0
  • Webpack: ^4.29.0
"scripts": {
        "dev": "NODE_ENV=development webpack-dev-server --config scripts/webpack.config.js",
        "build": "NODE_ENV=production webpack --config scripts/webpack.config.js",
        "ts-build": "tsc --pretty --outDir ./modules",
        "ts-watch": "tsc -w --pretty --outDir ./modules",
        "pm2-dev": "pm2 start npm --name prdemo -- run dev && npm run ts-watch",
        "pm2-clear": "pm2 delete prdemo"
    },
    "husky": {
        "hooks": {
            "pre-commit": "lint-staged"
        }
    },
    "lint-staged": {
        "linters": {
            "*.{js,jsx,md,json}": [
                "prettier --write",
                "git add"
            ],
            "*.{ts,tsx}": [
                "prettier --write",
                "tslint --fix",
                "git add"
            ],
            "*.css": [
                "stylelint --fix",
                "git add"
            ],
            "*.less": [
                "stylelint --fix --syntax=less",
                "git add"
            ]
        },
        "ignore": [
            "./modules/**"
        ]
    },
    "eslintConfig": {
        "extends": "eslint-config-aerian"
    },
    "eslintIgnore": [
        "build/*"
    ],
    "stylelint": {
        "extends": "stylelint-config-standard",
        "rules": {
            "indentation": 4,
            "number-leading-zero": null,
            "at-rule-no-unknown": [
                true,
                {
                    "ignoreAtRules": [
                        "plugin"
                    ]
                }
            ],
            "selector-pseudo-class-no-unknown": [
                true,
                {
                    "ignorePseudoClasses": [
                        "global"
                    ]
                }
            ]
        }
    },
    "dependencies": {
        "preact": "^8.2.6"
    },
    "devDependencies": {
        "@types/jest": "^23.3.13",
        "@types/node": "^8.10.38",
        "copy-webpack-plugin": "^5.0.0",
        "css-loader": "^2.1.0",
        "file-loader": "^3.0.1",
        "html-webpack-plugin": "^3.2.0",
        "husky": "^1.3.1",
        "jest": "^24.0.0",
        "less": "^3.9.0",
        "less-loader": "^4.1.0",
        "lint-staged": "^8.1.1",
        "mini-css-extract-plugin": "^0.5.0",
        "mkdirp": "^0.5.1",
        "preact-async-route": "^2.2.1",
        "preact-render-spy": "^1.3.0",
        "preact-router": "^2.6.1",
        "prettier": "^1.16.1",
        "recursive-copy": "^2.0.10",
        "style-loader": "^0.23.1",
        "stylelint": "^9.10.1",
        "stylelint-config-standard": "^18.2.0",
        "ts-jest": "^23.10.5",
        "ts-loader": "^5.3.3",
        "tslint": "^5.12.1",
        "tslint-config-prettier": "^1.17.0",
        "tslint-consistent-codestyle": "^1.15.0",
        "tslint-eslint-rules": "^5.4.0",
        "tslint-react": "^3.6.0",
        "typescript": "^3.2.4",
        "typings-for-css-modules-loader": "^1.7.0",
        "uglifyjs-webpack-plugin": "^2.1.1",
        "url-loader": "^1.1.2",
        "webpack": "^4.29.0",
        "webpack-cli": "^3.2.1",
        "webpack-dev-server": "^3.1.14"
    }

之所以加了pm2,是因为有两份TypeScript源码需要watch,Webpack本身watch占用一个console窗口,tsc自己也要有。如果一个命令启动,就会导致一个卡在另一个之前,导致另一个无法启动…..所以索性把webpack直接放到了后台。其本身作为Server服务,也算合理。不过正常情况,不会像我这么变态……

二. TSC

{
    "compilerOptions": {
        "charset": "utf8",
        "sourceMap": true,
        "rootDir": "./src/",
        "outDir": "./modules",
        "skipLibCheck": true,
        "allowSyntheticDefaultImports": true,
        "target": "es5",
        "module": "es6",
        "lib": ["es6", "es7", "dom"],
        "allowJs": false,
        "jsx": "react",
        "jsxFactory": "h",
        "strict": true,
        "declaration": true,
        "moduleResolution": "node",
        "noImplicitAny": false,
        "esModuleInterop": false,
        "experimentalDecorators": true,
        "removeComments": false,
        "preserveConstEnums": true
    },
    "include": ["./src/**/*"],
    "exclude": [
        "docs",
        "modules",
        "typings",
        "node_modules"
    ]
}

注意jsxFactory使用的是h,这个hPreact的核心之一

三. Less的一点问题

1,正常使用import引用less时,IDE会报错提示Cannot find module './style.less'.。直接加@ts-ignore可以忽略,但不是好方法

// @ts-ignore
import style from './style.less';

如上所以,解决方式也很简单:第一新建less.d.ts文件,第二在该文件内申明less

declare module "*.less";

然后,在基类中引用,注意不是import,而是reference指令

/// <reference path="../../typings/less.d.ts" />

2,组件上,用点语法使用less的选择器。但又得注意,style.className的值是随机字符串,并非对象,因此不能接着点(内层嵌套拿不到值),即:style.className.Name1非法

a {
    &.normal {
        color: #333;
    }
    &.active {
        color: #fff;
    }
}

以上用法如下:

<nav class={isActive ? style.active : style.normal}>{txt}</a>

四. Preact无状态组件使用

import {FunctionalComponent, h} from 'preact';
import style from './style.less';

interface INavProps {
    transparent?: boolean,
    title?: string,
    onBack?: (index) => void
    onHome?: (index) => void,
    onShare?: (index) => void
}
export const Navigator: FunctionalComponent<INavProps> = ({transparent, title, onBack, onHome, onShare}) => {
    const onBackPress = (e) => {
        if (onBack) {
            onBack(e);
            return;
        }
        // TODO.
    };
    const onHomePress = (e) => {
        if (onHome) {
            onHome(e);
            return;
        }
        // TODO.
    };
    const onSharePress = (e) => {
        if (onShare) {
            onShare(e);
            return;
        }
        // TODO.
    };
    const className = transparent ? style.transparent : style.normal;
    return (
        <nav class={className}>
            <button class={className} onClick={onBackPress} />
            <span class={className}>{title}</span>
            <button class={className} onClick={onHomePress} />
            <button class={className} onClick={onSharePress} />
        </nav>
    );
};

五. 其他方面

由于着手的这项目有点怪,所以自制了一款webpack插件,其中有用到了recursive-copy。需要把指定文件夹里的非TS和TSX拷贝到另外的目录,配置其filter时曾让人蛋疼不已…..具体请看minimatch。结论是这玩意跟一般正则不一样,当时按正则咋配咋不对

// 过滤ts和tsx
const OPTIONS = {
    overwrite: true,
    expand: true,
    dot: true,
    filter: ['**/*.!(tsx|ts)'],
};

扩展阅读: