likes
comments
collection
share

从0到1发布你的react组件(一): 创建组件基本项目

作者站长头像
站长
· 阅读数 49

前段时间写了篇文章:

React写input脱敏组件

后来想何不将这个组件发布到npm上,供更多的人使用呢?这篇文章主要是从0搭建react组件并将其发布到npm上

搭建框架

创建项目,初始化包
mkdir react-encrypted-input-demo
cd react-encrypted-input-demo
npm init
安装依赖

使用react开发

  • react相关依赖
npm install react react-dom

-js编译之类的依赖

npm install @babel/cli @babel/core @babel/preset-env @babel/preset-react babel-loader --save-dev

样式使用sass开发

  • 样式相关依赖
npm install sass sass-loader css-loader style-loader --save-dev

打包主要使用webpack,所以需要安装一些webpack相关的依赖

  • webpack相关的依赖
npm install webpack webpack-cli webpack-dev-server --save-dev
创建项目

创建好的目录结构为下:

react-encrypted-input-demo
├─ node_modules
├─ package-lock.json
├─ package.json
├─ src                   -- 源码
│  ├─ index.scss
│  ├─ index.js
│  ├─ assets
│  │  ├─ close-eye.png
│  │  └─ eye.png
│  └─ components       
│     ├─ index.scss
│     └─ index.js
├─ scripts              -- webpack打包命令
│  ├─ webpack.dev.config.js
│  ├─ webpack.prod.config.js
│  └─ webpack.base.config.js
└─ public
   └─ index.html
  • public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>react-encrypted-input-demo</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>
  • src/index.js
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";
import EncryptedInput from "./components/index";
import "./index.scss";
const App = () => {
  return (
    <div className="example">
      <div className="title">Example</div>
      <EncryptedInput
        initValue="我爱你啊梦"
        front="1"
        end="2"
        onChange={(d1, d2) => {
          console.log(d1, d2);
        }}
        mode="plain"
      />
    </div>
  );
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

  • src/index.scss
.example {
    margin: 20px 40px;
    border:1px solid #cfcfcf;
    .title {
        font-size: 30px;
    }
}

  • src/components/index.js
import "./index.scss";
import showIconDefault from "../assets/eye.png";
import closeIconDefault from "../assets/close-eye.png";

// 保存光标位置
let selectionStart = 0,
  selectionEnd = 0;

export default class EncryptedInput extends Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
    this.state = {
      show: this.props.mode === "plain" ? 1 : 0,
      value: "",
      mValue: "",
    };
  }

  componentDidMount() {
    this.init();
  }

  init = () => {
    if (this.props.initValue) {
      this.setState(
        {
          value: this.props.initValue,
          mValue: this.handleFormat(this.props.initValue),
        },
        () => {
          if (this.state.show) {
            this.inputRef.current.value = this.state.value;
          } else {
            this.inputRef.current.value = this.state.mValue;
          }
        }
      );
    }
  };

  // 脱敏处理
  handleFormat = (value) => {
    const { front = 0, end = 0 } = this.props;
    if (!value) {
      this.setState({
        mValue: "",
      });
      return "";
    }
    let str = "";
    const len = value.length;
    const star = Math.max(0, len - (Number(front) + Number(end)));
    if (len <= Number(front) + Number(end)) {
      str = value;
    } else {
      str =
        value.slice(0, Number(front)) +
        "*".repeat(star) +
        value.slice(Number(front) + star);
    }
    ``;
    this.setState({
      mValue: str,
    });
    return str;
  };

  handleChange = (e) => {
    if (this.inputRef.current.cnIputFlag) return;
    // 获取光标
    selectionStart = e.target.selectionStart;
    selectionEnd = e.target.selectionEnd;
    // 光标位置
    const ind = selectionStart - 1;
    let actualVal = this.state.value || "";
    let currentVal = e.target.value;
    const isAdd = currentVal.length > actualVal.length;
    const num = Math.abs(currentVal.length - actualVal.length);
    if (isAdd) {
      actualVal =
        actualVal.slice(0, ind - num + 1) +
        currentVal.slice(ind - num + 1, ind + 1) +
        actualVal.slice(ind - num + 1);
    } else {
      actualVal = actualVal.slice(0, ind + 1) + actualVal.slice(ind + num + 1);
    }
    this.setState(
      {
        value: actualVal,
      },
      () => {
        const value = this.state.value,
          mValue = this.handleFormat(this.state.value);
        if (this.state.show) {
          this.inputRef.current.value = value;
        } else {
          this.inputRef.current.value = mValue;
        }
        if (this.inputRef.current) {
          this.inputRef.current.setSelectionRange(selectionStart, selectionEnd);
        }
        if (this.props.onChange) {
          this.props.onChange(value, mValue);
        }
      }
    );
  };
  handleSetValue() {
    if (this.inputRef.current) {
      if (this.state.show) {
        this.inputRef.current.value = this.state.value;
      } else {
        this.inputRef.current.value = this.state.mValue;
      }
    }
  }
  // 主要为了校验是否在进行中文输入
  composition = (e) => {
    if (e.type === "compositionend") {
      this.inputRef.current.cnIputFlag = false;
      this.handleChange(e);
    } else {
      this.inputRef.current.cnIputFlag = true;
    }
  };

  changeIcon = () => {
    this.setState(
      {
        show: this.state.show ^ 1,
      },
      () => {
        if (this.state.show) {
          this.inputRef.current.value = this.state.value;
        } else {
          this.inputRef.current.value = this.handleFormat(this.state.value);
        }
      }
    );
  };

  render() {
    const {
      style,
      showIcon = showIconDefault,
      closeIcon = closeIconDefault,
    } = this.props;
    const { show } = this.state;
    return (
      <div className="react-encrypted-input-wrap">
        <input
          ref={this.inputRef}
          style={style?.input || {}}
          onChange={(e) => this.handleChange(e)}
          onCompositionStart={this.composition}
          onCompositionEnd={this.composition}
        />
        <img
          className="icon"
          src={show ? showIcon : closeIcon}
          onClick={this.changeIcon}
          style={style?.icon || {}}
        />
      </div>
    );
  }
}

  • src/components/index.scss
.react-encrypted-input-wrap {
    display: flex;
    align-items: center;
    .icon {
       margin-left:5px;
    }
}

  • scripts/webpack.base.config.js

这里处理图片主要用了url-loader,所以需要安装下url-loader : npm install url-loader --save-dev

module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: "babel-loader",
        exclude: /node_modules/,
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.scss$/,
        use: ["style-loader", "css-loader", "sass-loader"],
      },
      {
        test: /\.png$/,
        use: ["url-loader"],
      },
    ],
  },
};

  • scripts/webpack.dev.config.js

安装依赖: npm install html-webpack-plugin webpack-merge --save-dev

const path = require('path');
const webpack = require('webpack');
const webpackConfigBase = require('./webpack.base.config');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { merge } = require('webpack-merge');

function resolve(relatedPath) {
    console.log(__dirname)
    return path.join(__dirname, relatedPath)
}

const webpackConfigDev = {
    mode: 'development',

    entry: {
        app: [resolve('../src/index.js')],
    },

    output: {
        path: resolve('../dist'),
        filename: 'index.js',
    },

    devtool: 'eval-cheap-module-source-map',

    devServer: {
        hot: true,
        open: true,
        host: 'localhost',
        port: 3019,
    },

    plugins: [
        new HtmlWebpackPlugin({ template: './public/index.html', }),
    ]
}

module.exports = merge(webpackConfigBase, webpackConfigDev)

  • scripts/webpack.prod.config.js 安装依赖:npm install webpack-node-externals terser-webpack-plugin clean-webpack-plugin --save-dev
const path = require('path');
const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');
const webpackConfigBase = require('./webpack.base.config');
const TerserJSPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const { merge } = require('webpack-merge');

function resolve(relatedPath) {
    return path.join(__dirname, relatedPath)
}

const webpackConfigProd = {
    mode: 'production',

    entry: {
        app: [resolve('../src/components/index.js')],
    },

    output: {
        filename: 'index.js',
        path: resolve('../lib'),
        libraryTarget: 'commonjs2',
    },

    devtool: 'source-map',  //或使用'cheap-module-source-map'、'none'
    optimization: {
        minimizer: [
            // 压缩js代码
            new TerserJSPlugin({// 多进程压缩
                parallel: 4,// 开启多进程压缩
                terserOptions: {
                    compress: {
                        drop_console: true,   // 删除所有的 `console` 语句
                    },
                },
            }),
        ],
    },
    externalsPresets: { node: true },
    externals: [nodeExternals()],

    plugins: [
        new CleanWebpackPlugin() //每次执行都将清空一下目录
    ]
}
module.exports = merge(webpackConfigBase, webpackConfigProd)

  • 添加babel配置文件

.babelrc

{
    "presets": ["@babel/preset-env", "@babel/preset-react"]
}

  • 修改package.json中scripts的脚本
"build": "webpack --config ./scripts/webpack.prod.config.js",
"start": "webpack-dev-server --config ./scripts/webpack.dev.config.js"

在终端输入: npm run start

此时,可以看到项目已经正常运行了

从0到1发布你的react组件(一): 创建组件基本项目

下一篇文章,将会讲述如何将这个已经创建好的项目发布到npm上

从0到1发布你的react组件(二): 发布项目到npm

转载自:https://juejin.cn/post/7253710675181092923
评论
请登录