preface

Recently, I encountered a demand for multi-level drop-down in business, which required to display the back-end tree data on textaREA, and the textaREA can also process the data and convert it into multi-level selection tree data.

Take the multi-level drop-down of the questionnaire star as an example. As shown in the figure below, users can compile multi-level drop-down data in the textArea box. The first line represents the title, and each remaining line represents the data of each level in a multi-level drop-down box.

After the data is edited and saved, we use the tree data on the mobile side or applet side, thus completing a multi-level drop-down component.

This article will briefly introduce this workflow, including:

  • How willTree dataintotextareaShow thevalueValue?
  • How willtextareaIs converted toTree data
  • How do you determine which data already exists, which is new, and which is deleted?
  • How to test a release tonpmThe components?

The data presentation of the multi-level pull-down is not covered in this article, so let’s start.

 

Project description

Project Preview

 

Technology stack

React-hooks are the future of React. TypeScript is the future of JavaScript. React-hooks are the future of React.

The packaging tool uses Rollup because the Rollup of the packaged component library is more popular than Webpack, which is better for packaging complex, large projects. About the study of Webpack, we can refer to the author of the Webpack study document.

 

The project structure

The project structure is as follows:

. ├ ─ ─ node_modules// Third party dependencies├ ─ ─ example// Preview code at development time├ ─ ─ the public// Place the static resources folder├ ─ ─ the SRC// Sample code directory├ ─ ─ app. Js// Test project entry js file└ ─ ─ index. HTML// Test the project entry HTML file├ ─ ─ yarn. The lock// Test project YARN Lock file└ ─ ─ package. Json// Test project dependencies├ ─ ─ the SRC// Component source directory├ ─ ─ the components// The directory of wheels├ ─ ─ the textarea// a Textarea component for internal use in the project├ ─ ─ index. Less// Component core code style file└ ─ ─ textarea. TSX// Component core code├ ─ ─ types// Typescripe interface definition├ ─ ─ utils// Directory of utility functions├ ─ ─ assets// Static resource directory├ ─ ─ index. The TSX// Project entry file└ ─ ─ index. Less// Project entry style file├ ─ ─ lib// Component package results directory├ ─ ─ the test// Test the folder├ ─ ─ typings// Place the project global TS declaration file directory├ ─ ─ babelrc// Babel configuration file├ ─ ─ eslintignore// Eslintignore configuration file├ ─ ─. Eslintrc. Js// eslint configuration files├ ─ ─ gitignore// git files ignored while uploading├ ─ ─ API - extractor. Json// Use to combine multiple TS declaration files into one├ ─ ─ jest. Config. Js// Test the configuration file├ ─ ─ npmignore// NPM uploads ignored files├ ─ ─ the README. Md ├ ─ ─ tsconfig. Eslint. Json// ts eslint file├ ─ ─ tsconfig. Json// Ts configuration file├ ─ ─ a rollup. Config. Js// rollup packages configuration files├ ─ ─ yarn. The lock// yarn lock file└ ─ ─ package. Json// The current dependency of the entire project
Copy the code

For the project in addition to the source code of some knowledge points, I will not go into details, such as Rollup configuration; Api-extractor how to generate a number of declaration files and so on, we can consult a wave.

 

The warehouse address

The warehouse address is here: multi-level drop down Textarea components

 

HooksSkeletal code

This component needs to be supported by the following functions before writing it: React Hooks

<TreeTextArea
  treeTitle={title} // Multi-level drop down header data
  treeData={tree_value} // Tree data
  row={21} // The number of lines for the textarea
  showNumber // Whether to display the left textarea number
  shouleGetTreeData // Whether to enable the tree data processing function
  delimiter='/'     // Cut with what symbol
  maxLevel={4}      // Maximum series supported
  onChangeTreeData={  // Use with shouleGetTreeData to return the processed title and tree data
    (treeTitle, treeData) => {
      console.log('---treeTitle---', treeTitle);
      console.log('---treeData---', treeData);
    }
  }
  defaultData={DEFAULT_TEXT} // Tree data defaults
  placeholder=Please enter the title, for example: Province/city/district/county/school 
 Zhejiang Province ningbo City Jiangbei District School 1'
/>
Copy the code

We are in the SRC/components/textarea. Benchmark in the preparation of the corresponding code, including receiving value corresponding to the corresponding props, just enter the page to initialize the data, to monitor data changes and access tree value operation:

const TreeTextArea = (props: Props): JSX.Element => {
  // Accept a series of props data
  // ...
  const [__textAreaData, setTextAreaData] = useState(' ');
  const [__flattenData, setFlattenData] = useState([]);
  
  // Data initialization
  useEffect((a)= >{
    if (isArray(__treeData) && isArray(__treeTitle)) {
      // ...

      const flattenData = flattenChainedData(__treeData);
      const textAreaData = getTextAreaData(flattenData, titles);

      setFlattenData(flattenData);
      setTextAreaData(textAreaData.join('\n'));
    }

    return (a)= >{
      // willUnMount}}, [])// Listen for data changes
  const onChange = (data: any): void= > {}
  
  // Set the default value
  const getDefaultData = (): void= > {}
  
  // Get the tree value
  const getTreeData = (e: any): void= > {
    const { onChangeTreeData } = props;
    // ...
    if(onChangeTreeData) { onChangeTreeData(levelTitles, valueData); }}return (
    <div className={styles.wrapper}>
      <NumberTextArea
        row={__row}
        value={__textAreaData}
        onChange={onChange}
        showNumber={__showNumber}
        placeholder={__placeholder}
        errCode={__errCode}
        errText={__errText}
      />{/ /... // Fill the default value, get the tree value code}</div>)}Copy the code

We also package a NumberTextArea internally, in this component I added the left number display, error display logic, the specific code is not posted, you can refer to the source code.

React-hooks: React-hooks: React-hooks: React-hooks: React-hooks: React-hooks

Let’s take a look at the core multi-level pull-down logic in a component.

 

Multi-level drop-down logic core code

Once the skeleton code is in place, all we need to do is focus on the logic of the textarea processing the data.

First, create testdata. ts in the utils directory to simulate the json data on the back end, as shown below:

 

Data rendering

Let’s start with editing. Suppose the back end gives us a title to render and a multi-level drop-down tree of data:

Then we want to modify the data from the back end to a value that can be displayed in a textarea with some processing, similar to the following string as a value:

What we need to do here is to flatten the tree data and add a title attribute to each level of data. This is the data we need to display in each line of the textarea, like the following data:

We need to flatten out the data at each level.

However, there is a problem here, such as Zhejiang province/Ningbo city and Zhejiang Province/Ningbo City/Haishu District, which are two different data, but there is no need to show the data of Zhejiang Province/Ningbo City in textarea, so I made a judgment here, if there is a child in this data, I’m just going to add a hasChildren attribute to it, and let’s just do some filtering when we get the textarea data, and not show the data with the hasChildren attribute.

So how do we flatten the data? You just have to do some recursion on the tree.

@param {Array} data: tree_node data */
export const flattenChainedData = (data: any) = > {
  let arr = [];

  forEach(data, (item) => {
    const childrens = item.children;
    const rootObj = createNewObj(item, item.value);
    if (childrens) {
      rootObj.hasChildren = true;
    }

    arr.push(rootObj);

    if (childrens) {
      // Get all flat data recursively
      const dataNew = getChildFlattenData(childrens, item.value, 1); arr = concat(arr, dataNew); }});return arr;
};

@param {*} data: the array to be processed * @param {*} title: the title of the previous level * @param {*} level: the current level */
const getChildFlattenData = (data, title, level) = > {
  // The maximum level is exceeded
  if (level > MAX_LEVEL) return false;
  if(! data)return false;

  let arr = [];

  forEach(data, (item) => {
    const { children } = item;
    const rootObj = createNewObj(item, `${title}/${item.value}`);

    if (children) {
      rootObj.hasChildren = true;
      const childrenData = getChildFlattenData(children, `${title}/${item.value}`, level + 1);
      arr.push(rootObj);
      arr = concat(arr, childrenData);
    } else{ arr.push(rootObj); }});return arr;
};
Copy the code

CreateNewObj adds a value/title attribute to the flat data and returns a new object, without specifying the code.

After converting to flat data, we can remove the title attribute from the data and form the data required for textarea:

/** * flattening data into a textarea value * @param {Array} flattening data * @param {String} titles: title on the first line of a textarea */
export const getTextAreaData = (flattenData, titles) = > {
  const newData = filter(flattenData, (item) => {
    return! item.hasChildren && item.status ! = =2;
  });

  const arr = [];

  arr.push(titles);

  forEach(newData, (item) => {
    arr.push(item.title);
  });

  return arr;
};
Copy the code

Where we filter hasChildren to true, we also filter the deleted data with status = 2, and we get an array of textarea as shown in the following figure:

We then join these arrays with the \n newline character to get the desired textarea value.

 

Tree data processing

This is the most core part of the whole multi-level drop-down logic. We need to associate the data modified by the user with the original data to generate new tree data. We’re going to explain this in four steps.

We create a data processing class called treeTextAreaDataHandle and initialize an instance object by passing in the flattened data, textarea text box value in the constructor constructor. Later we will refine some of our properties and methods for handling data in this class.

class treeTextAreaDataHandle {
  // Flatten the array
  private flattenData: FlattenDataObj[];

  // Textarea box text value
  private textAreaTexts: string;

  constructor(options: treeTextAreaData) {
    const { flattenData, textAreaTexts } = options;

    this.flattenData = flattenData;
    this.textAreaTexts = textAreaTexts; }}Copy the code

Generate the modified initial mapping

In the first step, we will generate the initial mapping data of textarea modified by the user. We will put them in different arrays according to the series. For example, 🌰 :

We will generate a group of object data arranged according to the series according to the textarea value entered by the user at last, as shown below:

For example, the figure above will be converted to the data in the figure below, which will be the key for the next three steps:

Need to be aware of a problem here, is likely to exist in a certain level is the value of the same name, this time we can’t simply take the two values that are the same values, and to compare their father is the same, and so on, recursively until the first level, if is the same, so they are the same value, or different values.

For example, if there are two data values: Zhejiang/Ningbo/Haishu district and Jiangsu/Wuxi/Haishu District, although the name of Haishu district in the third level is the same, they are two different values. They should be assigned two different ids and their parent_id is different.

TransDataFromText = transDataFromText = transDataFromText = transDataFromText = transDataFromText

** * merge textarea data into a mathematical structure * @param {Array} flattenData: flat data * @param {String} texts: textarea */
public transDataFromText() {
  const texts = this.textAreaTexts;

  const arr = texts.split('\n');

  // remove the title
  if (arr.length > 1) {
    arr.shift();
  }

  // Assign each line of text to an array
  this.textAreaArr = arr;

  // Parse TextArea data into the specified hierarchy mapping data
  this.parserRootData();

  // ...
}
Copy the code

We generate the modified initial mapping in the parserRootData method:

@param {Array} textArr: Array of textarea text converted * @param {Number} handleLevel: The series to deal with */
private parserRootData() {
  // The textArea value for each line
  const textArr  = this.textAreaArr;
  // Maximum series
  const handleLevel = this.MAX_LEVEL;
  // Cut with what delimiter
  const delimiter = this.delimiter;

  // Remove the textArea value at each level
  const uniqueTextArr = uniq(textArr);

  // Map data store object
  const namesArrObj: namesArrObj = {};

  // Create an array for each level based on the maximum level
  for (let i = 1; i <= handleLevel; i++) {
    namesArrObj[`${ROOT_ARR_PREFIX}_${i}`] = [];
  }

  // Iterate over the textArea value for each line
  forEach(uniqueTextArr, (item: string) => {
    // Cut each line of string to generate a string
    const itemArr = item.split(delimiter);
    
    // Plug data to namesArrObj according to the maximum series
    for (let i = 1; i <= handleLevel; i++) {
      if (
        !treeTextAreaDataHandle.sameParentNew(namesArrObj, itemArr, i)
        && itemArr[i - 1]) {// Create an object of the corresponding series, and stuff it into the corresponding array
        const obj: parserItemObj = {};
        obj.id = _id();
        obj.value = itemArr[i - 1];
        obj.level = i;

        // Get the current series value, the father's ID
        const parentId = treeTextAreaDataHandle.getParentIdNew(
          namesArrObj, itemArr, i
        );
        
        obj.parent_id = parentId;
        
        namesArrObj[`${ROOT_ARR_PREFIX}_${i}`].push(obj); }}});// Save to the object's rootArrObj property value
  this.rootArrObj = namesArrObj;
}
Copy the code

One of the most critical methods above is the static method sameParentNew, which helps us recursively determine whether two values with the same name are really the same. In fact, the principle is simple, but also recursive to determine whether their respective fathers are the same. Specific code we can refer to the source code.

Secondly, there are also uses like:

  • createidMethod:_id()
  • Looking for aparent_idStatic method of:getParentIdNew

The first step was to generate the initial mapping data, then we needed to combine the flattening data that the back end provided us to populate the existing data and sift out the new data.

 

Populate existing data and filter new data

In this step we had to compare the data data that the back end gave us to our initial mapping data. Fill in the existing data attributes and filter out the new data, add the attribute new = true to the new data, and finally insert the corresponding object corresponding to the series group existNamesArrObj and addNamesArrObj.

An 🌰

We added Zhejiang Province/Ningbo City/high-tech zone, we can find high-tech zone in the third level of the new data, because Zhejiang province and Ningbo city already exist, they will not be added to the new array, they will only be found in the existing object. And it will replace the generated ID of the mapping data we generated before with the ID given by the backend, as shown below:

Existing data:existNamesArrObj

New data:addNamesArrObj

Another point to note here was that before we screened the data, we needed to add a root_id attribute to the data provided by the back end, which helped us correlate the revised data with the data provided by the back end. For example, we added high-tech fields above. His father already exists, and his ID is already 36178, but the parent_id of the newly added high-tech zone is generated when we map data, the two are definitely not equal, we need to use root_id to connect the two data.

So let’s go ahead and put this wave of processing in our handleExistData method,

* @param {*} TextAreaData: parserRootData() processed data * @param {*} newflattening data: Flat data * @param {Number} handleLevel: series to process */
private handleExistData() {
  const namesArrObj = this.rootArrObj;
  const newFlattenData = this.flattenData;
  const handleLevel = this.MAX_LEVEL;

  // Existing data
  const existNamesArrObj = {};
  // Add new data
  const addNamesArrObj = {};

  for (let i = 1; i <= handleLevel; i++) {
    addNamesArrObj[`${ADD_ARR_PREFIX}_${i}`] = [];
    existNamesArrObj[`${EXIST_ARR_PREFIX}_${i}`] = [];
  }

  // Flatten plus the mapping ID of parser
  this.setMapIdForFlattenDataToRootData();

  for (let i = 1; i <= handleLevel; i++) {
    // Get the corresponding series data of the accident map
    const curNamesArr = namesArrObj[`${ROOT_ARR_PREFIX}_${i}`];

    forEach(curNamesArr, (item) => {
      // Set a flag bit
      // Indicates whether this level of data exists
      let flag = false;
			
      // Map data attributes
      const { value, parent_id, id } = item;

      // Add data obj
      const addNewObj: addNewObj = {
        level: i,
        value,
        id,
        new: true.root_id: id,
      };

     	// Iterate over 'value' and 'level' to compare backend data with mapped data
      // to determine if their mapping data exists
      / / is
      forEach(newFlattenData, (val) => {
        if (value === val.value) {
          if (val.level === i) {
            // Level equals 1
            if (val.level === 1 && val.parent_id === 0) {
              constobj = { ... val }; existNamesArrObj[`${EXIST_ARR_PREFIX}_${i}`].push(obj);
              flag = true;
            }
            // Level is greater than 1
            if(val.level ! = =1|| val.parent_id ! = =0) {
              if (this.isExistitem(val, parent_id, i)) {
                constobj = { ... val }; existNamesArrObj[`${EXIST_ARR_PREFIX}_${i}`].push(obj);
                flag = true; }}}}});// If new data is added
      if(! flag) {/ / into addNamesArrObj
        addNamesArrObj[`${ADD_ARR_PREFIX}_${i}`].push(addNewObj);
        // Insert the latest flattening datanewFlattenData.push(addNewObj); }}); }// Attach existNamesArrObj to the class attribute existNamesArrObj
  this.existNamesArrObj = existNamesArrObj;
  this.addNamesArrObj = addNamesArrObj;
}
Copy the code

Another important method isExistitem method is used in the above method to determine whether the current level data exists. Its principle is similar to sameParentNew used in parserRootData, and it is also to recursively compare whether their fathers are the same. Until you find a different dad or get to level 1, where you’re comparing the initial mapping data to the back-end flat data.

And a method is that we talked about the method of adding root_id data to back-end flat setMapIdForFlattenDataToRootData, specific code the author posted, don’t you are interested can look at it.

After processing the existing data and the new data, we also need to process the deleted data.

Here if you need to make the right sort data, can it exist in data and new data on an object, so that every time a new or existing data from both in turn into, the author is now exist to separate the data and new data and new data by default are in the final.

 

Processing deleted data

In general, if data is deleted, the front end also needs to pass the data to the back end to tell the back end that the data is deleted. Therefore, we need to add the corresponding status value to the deleted data. Here, we add status = 2, indicating that the data has been deleted in the front end.

This is easy to implement because we already have the existing data in step 2. We just need to compare it with the flat data ** provided by the original ** back end. Then we can figure out which data was deleted and filter them to the corresponding attributes.

For example, we deleted the line of Jiangsu Province/Wuxi City/Huishan District. Actually, we deleted the data of Wuxi city and Huishan District, and we can get the following results:

The following code, we will delete data filtering method of writing in handleTagForDeleleByLevel approach,

* @param {*} handleDataArr: * @param {*} newflattening data * @param {Number} handleLevel: a wave to process */
private handleTagForDeleleByLevel = (a)= > {
  const existNamesArrObj = this.existNamesArrObj;
  const handleLevel = this.MAX_LEVEL;

  // Store an array with flat data
  let existData = [];

  // Traversal existing data object flat existing data
  for (let i = 1; i <= handleLevel; i++) {
    const curArray = existNamesArrObj[`${EXIST_ARR_PREFIX}_${i}`];
    existData = concat(existData, curArray);
  }

  // Add attribute status = 2 to delete data
  const deleteData = this.addTagForDeleleData(existData);

  // Attach deleteData to the property deleteData
  this.deleteData = deleteData;
};
Copy the code

We compare the different values with the addTagForDeleleData method and add the status=2 attribute. Here we can also use lodash’s difference method to get the different values of the two arrays.

After the above three steps have been processed, you are basically done, and the final tree data is generated.

 

Generate tree data

Finally, we need to take the existing data, add data, delete data to create a new flat array, and from this new flat data to generate the desired tree data.

For example, if we add Zhejiang province/Ningbo City/High-tech Zone and delete Jiangsu Province/Wuxi City/Huishan District, we will get the new flat data as follows. We can see that high-tech zone is new and Huishan District also adds the corresponding status=2 attribute:

Then we can recursively generate tree data according to the flat data, as shown in the following figure:

Next, the code starts with the getlastdata method, which collects the latest flattening data:

@param {*} existNamesArrObj: existNamesArrObj: existNamesArrObj: existNamesArrObj: existNamesArrObj: existNamesArrObj: existNamesArrObj: existNamesArrObj: existNamesArrObj: existNamesArrObj AddNamesArrObj add data * @param {*} deleteData: addTagForDeleleByLevel() deleteData * @param {Number} handleLevel: series to process */
private getLastFlattenData() {
  const existNamesArrObj = this.existNamesArrObj;
  const newAddNamesArrObj = this.newAddNamesArrObj;
  const deleteData = this.deleteData;
  const handleLevel = this.MAX_LEVEL;

  let lastData = [];

  let AddLast = [];
  let ExistLast = [];

  // Iterate over flat existence and new data
  for (let i = 1; i <= handleLevel; i++) {
    const curArrayExist = existNamesArrObj[`${EXIST_ARR_PREFIX}_${i}`];
    const curArrayAdd = newAddNamesArrObj[`${HANDLE_ADD_ARR_PREFIX}_${i}`];

    ExistLast = concat(ExistLast, curArrayExist);
    AddLast = concat(AddLast, curArrayAdd);
  }

  // Merge three types of data
  lastData = concat(lastData, ExistLast, AddLast, deleteData);

  // Attach lastData to the class property newDataLists
  this.newDataLists = lastData;
};
Copy the code

Finally, the final tree data is generated. The principle is to perform recursive traversal starting from parent_id 0 until all nodes are traversed. At the same time, we need to delete some unnecessary attributes, such as new attribute and mapping association root_id, before generating the tree.

* @param {Object} item: item for each item */
static clearParamsInTreeData = (item) = > {
  delete item.title;
  delete item.hasChildren;
  delete item.root_id;

  if (item.new) {
    delete item.new;
    delete item.id;
    deleteitem.parent_id; }};/** * Recursion converts flat data to tree-structured data * for transDataFromText * @param {Array} lists: Flat data * @param {Number} parent_id: id of dad */
private getTreeDataBylists = (parent_id: number | string): any= > {

  const lists = this.newDataLists;

  // Recursive, menu
  const tree = [];

  forEach(lists, (item) => {
    const newItemId = item.parent_id;

    if (parent_id === newItemId) {
      const childrenTree = this.getTreeDataBylists(item.id);
      if (isArray(childrenTree) && childrenTree.length > 0) {
        item.children = childrenTree;
      } else {
        item.children = null;
      }

      // Delete unnecessary attributestreeTextAreaDataHandle.clearParamsInTreeData(item); tree.push(item); }});return tree;
};
Copy the code

At this point we have finished processing the textarea, and the final transDataFromText method is as follows:

** * merge textarea data into a mathematical structure * @param {Array} flattenData: flat data * @param {String} texts: textarea */
public transDataFromText() {
  const texts = this.textAreaTexts;

  const arr = texts.split('\n');

  if (arr.length > 1) {
    arr.shift();
  }

  this.textAreaArr = arr;

  // Parse TextArea data into the specified hierarchy mapping data
  this.parserRootData();

  // Populates existing data and filters new data
  this.handleExistData();

  // Process new data
  this.handleParamsInAddData();

  // Get delete data
  this.handleTagForDeleleByLevel();

  // Get the latest flat data
  this.getLastFlattenData();

  // Get the latest tree data
  this.lastTreeData = this.getTreeDataBylists(0);

  return this.lastTreeData;
}
Copy the code

 

Error handling

We need to deal with some mistakes, such as Users may not enter the title of the title, or user input is greater than the maximum support series (of course in our project, the biggest can support series users to control), or the title of series and the series is not corresponding to the content below, these should be classified as the list of errors.

For example, when the user does not enter the title, we should prompt him to enter the title, as shown below:

We will create a new method isEquelLevel method to check whether the value entered by the user conforms to the specification. The code is actually very simple. We can get the final data and traverse whether there is an error in the data. The error types are as follows:

/** * Check information */
export const ERROR_INFO = {
  1: 'First line header cannot be empty'.2: 'The first line heading must not exceed${MAX_LEVEL}Column `.3: 'Keep the number of levels of title and selection the same'.4: 'The selection cannot be exceeded${MAX_LEVEL}Line `.5: 'Please fill in at least one selection'};Copy the code

 

test

React-scripts: create-react-app react-scripts: create-react-app

package.jsonConfiguration:

Here is the test project package.json file:

{
  "name": "example"."version": "0.0.0"."description": ""."license": "MIT"."private": true."scripts": {
    "start": "react-scripts start"."build": "react-scripts build"."test": "react-scripts test --env=jsdom"."eject": "react-scripts eject",},"author": "Darrell"."dependencies": {
    "lodash": "^ 4.17.15"."react": "link:.. /node_modules/react"."react-dom": "link:.. /node_modules/react-dom"."react-scripts": "^ 3.4.1 track"
  },
  "browserslist": [
    "0.2%" >."not dead"."not ie <= 11"."not op_mini all"]}Copy the code

The react and react-dom dependencies need to use the dependencies in node_modules.

Plugins that use Hooks will fail if multiple React applications are used:

The main reason for this problem is that the first React version didn’t reach 16.8, or the third one had multiple React references in the project.

As for the second issue, Hooks do not conform to the specification and can be largely avoided once we install the eslint-plugin-react-hooks plugin. For more information on this issue you can refer to issure.

Then we go to ExMaple to install the appropriate dependencies and run YARN Start directly to get our project running.

 

The main code

Let’s create a new directory in example’s public directory

  • index.html: project template file, which is responsible for project displayhtmlfile

      
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="theme-color" content="# 000000">

    <link rel="manifest" href="%PUBLIC_URL%/manifest.json">

    <title>The test page</title>
  </head>

  <body>
    <noscript>
      You need to enable JavaScript to run this app.
    </noscript>

    <div id="root"></div>
  </body>
</html>

Copy the code
  • manifest.json: Write to the mobile terminalh5appICONS, theme colors, etc. are set in this file, and we can configure any wave here.

Create a new folder in the SRC directory:

  • index.js: Project entry file
  • index.css: Entry file style
import React from 'react'
import ReactDOM from 'react-dom'

import './index.css'
import App from './App'

ReactDOM.render(<App />, document.getElementById('root'))

Copy the code
  • App.js: Test component file
import React, { Component } from 'react'

// Test data
import { title, tree_value, DEFAULT_TEXT } from './testData'

import TreeTextArea from 'darrell-tree-textarea'

export default class App extends Component {
  render () {

    return (
      <div className='App'>
        <TreeTextArea
          treeTitle={title}
          treeData={tree_value}
          row={21}
          showNumber
          shouleGetTreeData
          delimiter='/'
          maxLevel={4}
          onChangeTreeData={
            (treeTitle.treeData) = >{ console.log('---treeTitle---', treeTitle); console.log('---treeData---', treeData); }} defaultData={DEFAULT_TEXT} placeholder=' placeholder '; Zhejiang Province ningbo City Jiangbei District School 1' /></div>)}};Copy the code

 

hairnpmPrevious tests

The above test file is written in our component project.

However, before sending packages, we need to test them in other projects. In this case, we can use NPM link.

  • It is first executed in the component’s root directorynpm linkTo introduce the component globallynode_modules.

  • In the directory where you want to use the component, reference the component with the following command:
NPM link <package name >Copy the code

🌰 : If we create a new project with create-react-app, we can run it in the project root directory:

npm link @darrell/darrell-tree-textarea
Copy the code

At this time we can have dependencies on our project in the project:

Because it is a reference to the project, this dependency contains everything in our plug-in project, including node_modules. Because we have @darrell/ Darrel-tree-textarea dependencies on node_modules, React dependencies on it, and React dependencies on my-app node_modules, This is why multiple React references are a problem.

This problem does not occur after we post to NPM because node_modules is not uploaded to NPM.

There are two solutions:

  • delete@darrell/darrell-tree-textareaUnder thenode_modulesBut it needs to be reinstalled each time
  • It is recommended to useIn themy-appUnder the project, change the configuration file to add allreactThe reference points to the same reference
alias: {
  // ...
  'react': path.resolve(__dirname, '.. /node_modules/react'),
  'react-dom': path.resolve(__dirname, '.. /node_modules/react-dom'),},Copy the code

For details about how to distribute packages, you can refer to this article: Implement antD pager from scratch (3) : Release NPM. This article has detailed introduction to the testing of components and the release of NPM, which is not covered in this article.

 

summary

This paper mainly describes how to write a TextaREA component that can process multi-level drop-down tree data. Overall, it is relatively simple. The difficulty of the whole component should be how to effectively recursively process data:

  • For example, how to flatten the tree data: process them recursivelychildren.
  • How to determine whether two data with the same name are the same: recursively determine whether their father is the same and so on

I also had some trouble with the component testing because of the React Hooks component that didn’t work. I thought it was because of Hooks, but it was because of multiple React references.

However, now that I think about the problem in retrospect, I find that the React error reminder is very clear. I can solve the problem step by step by following the error prompt.

Honestly, I want a thumbs up!

 

Refer to the content

  • Webpack learns documents
  • Implement class ANTD pager from scratch (3) : publish NPM
  • Website React Hooks
  • TypeScript and Rollup build libraries
  • React Hooks 【 nearly 1W words 】+ project combat
  • Interviewer (6): Did you write “Common front-end components”?