Implement tree component based on react + typescript

Implement tree component based on react + typescript

based on
react
+
typescript
achieve
tree
Component

github

  • Support unit testing
  • Support open and close
  • Support unselect all
  • Support dynamic loading
  • Support drag sort

process line

  • 1.1 Create a project and install dependencies
  • 1.2 webpack configuration initialization, support typescript
  • 2.1 Unit test environment construction
    1. Create tree menu
    1. Create tree menu
  • 5.1 Turn on and off the function-icons
  • 5.2 Turn on and off the function-keyToNodeMap
  • 5.3 Open and close function-images.d.ts image type declaration
  • 5.3 Open and close function-onCollapse
  • 5.4 Select/cancel/select all functions -parent
  • 5.5 loading dynamic loading
  • 5.6 Drag sort

1. Initialize the project

  • Project preview

1.1 Create a project

mkdir crx-react-tree cd crx-react-tree npm init -y touch .gitignore Copy code

1.2 Installation dependencies

  • cnpm i react @types/react react-dom @types/react-dom -S
react core package @types/react type declaration react-dom react's dom rendering package renders react elements to the page @types/react-dom react's dom rendering declaration file Copy code
  • cnpm i webpack webpack-cli webpack-dev-server -D
webpack core files webpack-cli command line file webpack-dev-server development server Copy code
  • cnpm i typescript ts-loader source-map-loader style-loader css-loader less-loader less file-loader url-loader html-webpack-plugin -D
ts-loader loads ts Source-map-loader can debug ts files for translating source-map files style-loader inserts the css file into the page file-loader url-loader The icon/binary file/picture of the loaded file html-webpack-plugin generates html files Copy code
  • cnpm i jest @types/jest ts-jest jest-junit enzyme @types/enzyme enzyme-adapter-react-16 @types/enzyme-adapter-react-16 -D
Unit test test coverage jest @types/jest type declaration ts-jest runs the ts version of the unit test file jest-junit unit testing tool enzyme test tool for react project @types/enzyme enzyme type declaration enzyme-adapter-react-16 @types/enzyme-adapter-react-16 Copy code
  • cnpm i axios express qs @types/qs -D
  • @types beginning with @types are all typeScript declaration files, you can enter
    node_modules/@types/XX/index.d.ts
    To view
Module nameHow to use
reactReact is a JavaScript library for creating user interfaces.
react-domThis package serves as the entry point to the DOM and server renderers for React. It is intended to be paired with the generic React package, which is shipped as react to npm.
webpackwebpack is a module bundler. Its main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resource or asset.
webpack-cliThe official CLI of webpack
webpack-dev-serverUse webpack with a development server that provides live reloading. This should be used for development only.
typescriptTypeScript is a language for application-scale JavaScript.
ts-loaderThis is the TypeScript loader for webpack.
source-map-loaderExtracts source maps from existing source files (from their sourceMappingURL).
style-loaderInject CSS into the DOM.
css-loaderThe css-loader interprets @import and url() like import/require() and will resolve them.
less-loaderA Less loader for webpack. Compiles Less to CSS.
lessThis is the JavaScript, official, stable version of Less.
file-loaderThe file-loader resolves import/require() on a file into a url and emits the file into the output directory.
url-loaderA loader for webpack which transforms files into base64 URIs.
html-webpack-pluginPlugin that simplifies creation of HTML files to serve your bundles
jestjest is a delightful JavaScript Testing Framework with a focus on simplicity.
jest-junitA Jest reporter that creates compatible junit xml files
ts-jestts-jest is a TypeScript preprocessor with source map support for Jest that lets you use Jest to test projects written in TypeScript.
enzymeJavaScript Testing utilities for React
enzyme-adapter-react-16Enzyme is a JavaScript Testing utility for React that makes it easier to test your React Components' output. You can also manipulate, traverse, and in some ways simulate runtime given the output.

1.3 Support typescript

  • First you need to generate a
    tsconfig.json
    File to tell
    ts-loader
    How to compile code TypeScript code
tsc --init copy the code

tsconfig.json

{ "compilerOptions" : { "target" : "es5" , "module" : "commonjs" , "jsx " : "react" , "outDir" : "./dist" , "rootDir" : "./src" , "noImplicitAny" : true , "esModuleInterop" : true }, "include" : [ "./src/**/*" , "./typings/**/*" ] } Copy code
parametermeaning
targetConvert to es5
moduleCode specification
jsxReact mode will generate React.createElement, no conversion operation is needed before use, the output file extension is .js
outDirSpecify output directory
rootDirSpecify the root directory
sourceMapWhen compiling the ts file into a js file, the corresponding sourceMap file is generated at the same time
noImplicitAnyIf true, when the TypeScript compiler cannot infer the type, it will still generate a JS file, but it will also report an error
esModuleInteropWhether to translate common.js module
includeNeed to compile directory

1.3 Support typescript

jest.config.js

module .exports = { verbose : true , //Redundant display detailed information clearMocks : true , //Clear mocks collectCoverage : true , //Collect test coverage information reporters : [ "default" , "jest-junit" ], //Reporter report format default unit test moduleFileExtensions : [ "js" , " jsx " , "ts" , "tsx" ], //module file extension moduleDirectories : [ "node_modules" ], //module directory transform : { //If the module is.Files ending with tsx need to be translated with ts-jest "^.+\\.tsx?$" : "ts-jest" , }, //Indicates the regular matching for unit testing: the file is in the __tests__ directory, or the file testRegex ending in jsx |tsx : "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx|tsx)$" , }; Copy code

1.4 Turn on and off functions

src/index.tsx

import React from "react" ; import ReactDOM from "react-dom" ; import Tree from "./components/tree" ; import data from "./data" ; ReactDOM.render( < Tree data = {data}/> , document .getElementById( "root" )); Copy code

src/typings.tsx

//Declare an interface, named Treedata, define the type of a variable, define the attribute name and attribute type of the object export interface TreeData { name : string ; key: string ; type : string ; collapsed: boolean ; children?: TreeData[]; //?: indicates optional attributes, which can be given or not parent?: TreeData; checked?: boolean ; } //Interface type, can be used to decorate or constrain component property object export interface Props { data : TreeData; onCollapse: any ; onCheck: any ; } export interface State { data : TreeData; } export interface keyToNodeMap { [key: string ]: TreeData; } Copy code

src/components/tree.tsx

import React from "react" ; import "./index.less" ; import {TreeData, Props, State, keyToNodeMap} from "../typings" ; import TreeNode from "./tree-node" ; class Tree extends React . Component < Props , State > { keyToNodeMap : keyToNodeMap; constructor ( props: Props ) { super (props); this .state = { data : this .props.data }; } componentDidMount () { this .buildKeyMap(); } buildKeyMap = () => { const data = this .state.data; this .keyToNodeMap = {}; this .keyToNodeMap[data.key] = data; if (data.children && data.children.length> 0 ) { this .walk(data.children, data); } this .setState({ data : this .state.data }); }; walk = ( children: TreeData[], data: TreeData ) => { children.forEach( ( item: TreeData ) => { item.parent = data; this .keyToNodeMap[item.key] = item; if (item.children && item.children.length> 0 ) { this .walk(item.children, item); } }); }; onCollapse = ( key: string ) => { let data = this .keyToNodeMap[key]; if (data) { data.collapsed = !data.collapsed; data.children = data.children || []; //later will be changed to lazy loading this .buildKeyMap(); } }; onCheck = ( key: string ) => { let data: TreeData = this .keyToNodeMap[key]; if (data) { data.checked = !data.checked; if (data.checked) { this .checkChildren(data.children, true ); this .checkParentCheckAll(data.parent); } else { this .checkChildren(data.children, false ); this .checkParent(data.parent, false ); } this .setState({ data : this .state.data }); } }; checkParentCheckAll = ( parent: TreeData ) => { while (parent) { parent.checked = parent.children.every( ( item ) => item.checked); parent = parent.parent; } }; checkParent = ( parent: TreeData, checked: boolean ) => { //It can be optimized, no need to find the vertex, just find the current node and the state passed in to stop the recursive search while (parent) { parent.checked = checked; parent = parent.parent; } }; checkChildren = ( children: Array <TreeData> = [], checked: boolean ) => { children.forEach( ( item: TreeData ) => { item.checked = checked; this .checkChildren(item.children, checked); }); }; render () { return ( < div className = "tree" > < div className = "tree-nodes" > < TreeNode onCheck = {this.onCheck} onCollapse = {this.onCollapse} data = {this.props.data} /> </div > </div > ); } } export default Tree; copy code

src/components/tree-node/tsx

import React from "react" ; import {TreeData, Props} from "../typings" ; import closedFolder from "../assets/closed-folder.png" ; import file from "../assets/file.png" ; import openedFolder from "../assets/opened-folder.png" ; class TreeNode extends React . Component < Props > { constructor ( props: Props ) { super (props); } render () { let { data : {name, children, collapsed = false , checked = false , key }, } = this .props; let caret, icon; if (children) { if (children.length> 0 ) { caret = ( < span className = { ` collapse ${ collapsed ?" caret-right " : " caret-down "}`} onClick = {() => this.props.onCollapse(key)} /> ); icon = collapsed? closedFolder: openedFolder; } else { caret = null ; icon = file; } } else { caret = ( < span className = { ` collapse caret-right `} onClick = {() => this.props.onCollapse(key)} /> ); icon = closedFolder; } return ( < div className = "tree-node" > < div className = "inner" > {caret} < span className = "content" > < input type = "checkbox" checked = {checked} onChange = {() => this.props.onCheck(key)} /> < img style = {{ width: 20 }} src = {icon}/> {name} </span > </div > {children && children.length> 0 && !collapsed && ( < div className = "children" > {children.map((item: TreeData) => ( < TreeNode onCheck = {this.props.onCheck} onCollapse = {this.props.onCollapse} key = {item.key} data = {item} /> ))) </div > )} </div > ); } } export default TreeNode; copy code

index.less

.tree { position : fixed; left : 0 ; top : 0 ; bottom : 0 ; width : 80% ; overflow-x : hidden; overflow-y : auto; background-color : #eee ; .tree-nodes { position : relative; overflow : hidden; .tree-node { .inner { color : #000 ; font-size :20px ; position : relative; cursor : pointer; padding-left : 10px ; .collapse { position : absolute; left : 0 ; cursor : pointer; } .caret-right : before { content : "\25B8" ; } .caret-down : before { content : "\25BE" ; } .content { display : inline-block; width : 100% ; padding : 4px 5px ; } } .children { padding-left : 20px ; } } } } Copy code

data.tsx

import {TreeData} from "./typings" ; const data: TreeData = { name : "father" , key : "1" , type : "folder" , collapsed : false , children : [ { name : "son 1" , key : "1-1" , type : "folder" , collapsed : false , children : [ { name : " 1" , key : "1-1-1" , type : "folder" , collapsed : false , children : [ { name : " 1" , key : "1-1-1-1" , type : "file" , collapsed : false , children : [], }, ], }, ], }, { name : "Son 2" , key : "1-2" , type : "folder" , collapsed : true , }, ], }; export default data; copy code

src/typings/images.d.ts

declare module "*.svg"; declare module "*.png"; declare module "*.jpg"; declare module "*.jpeg"; declare module "*.gif"; declare module "*.bmp"; declare module "*.tiff"; Copy code

1.5 check/cancel/select all functions

typings.tsx

export interface TreeData { name : string ; key: string ; type : string ; collapsed: boolean ; children?: TreeData[]; //?: indicates optional attributes, which can be given or not + parent?: TreeData; + checked?: boolean ; } export interface Props { data : TreeData; onCollapse: any ; + onCheck: any ; } Copy code

tree.tsx

buildKeyMap = () => { const data = this .state.data; this .keyToNodeMap = {}; this .keyToNodeMap[data.key] = data; if (data.children && data.children.length> 0 ) { this .walk(data.children, data); } this .setState({ data : this .state.data }); }; walk = ( children: TreeData[], data: TreeData ) => { children.forEach( ( item: TreeData ) => { item.parent = data; this .keyToNodeMap[item.key] = item; if (item.children && item.children.length> 0 ) { this .walk(item.children, item); } }); }; onCheck = ( key: string ) => { let data: TreeData = this .keyToNodeMap[key]; if (data) { data.checked = !data.checked; if (data.checked) { this .checkChildren(data.children, true ); this .checkParentCheckAll(data.parent); } else { this .checkChildren(data.children, false ); this .checkParent(data.parent, false ); } this .setState({ data : this .state.data }); } }; checkParentCheckAll = ( parent: TreeData ) => { while (parent) { parent.checked = parent.children.every( ( item ) => item.checked); parent = parent.parent; } }; checkParent = ( parent: TreeData, checked: boolean ) => { //It can be optimized, no need to find the vertex, just find the current node and the passed state to stop the recursive search while (parent) { parent.checked = checked; parent = parent.parent; } }; checkChildren = ( children: Array <TreeData> = [], checked: boolean ) => { children.forEach( ( item: TreeData ) => { item.checked = checked; this .checkChildren(item.children, checked); }); }; Copy code
<TreeNode onCheck={ this .onCheck} onCollapse={ this .onCollapse} data={ this .props.data} /> Copy code

tree-node.tsx

<input type = "checkbox" checked={checked} onChange={ () => this .props.onCheck(key)} /> render () { let { data : {name, children, collapsed = false , checked = false , key }, =} The this .props;} copy the code

1.6 loading dynamic loading

1.7 Drag sort