Click “like” and then look, wechat search [Big Move the world] pay attention to this person without dACHang background, but with a positive attitude upward. In this paper, making github.com/qq449245884… Has been included, the article has been categorized, also organized a lot of my documentation, and tutorial materials.

Everyone said there was no project on your resume, so I found one and gave it away【 Construction tutorial 】.

Introduction to the

This wheel is built with React + TypeScript + Webpack. Of course you can refer to my source code.

Here I also learn through others, mainly do some summary and explain a way of thinking about making each wheel, to facilitate the future use of other people’s wheels when their own mind has the idea of making wheels, to modify the source code to timely modify bugs, on time online.

The Icon Component of this article is written in reference to the Icon React Component in Framework7.

To read more quality articles pleaseJabber at the GitHub blog, a hundred quality articles a year waiting for you!

Why the wheel

1. Not asking for help

  • Let’s say you’re using a UI framework and you find a bug, and you give it back to the developer, and the developer says it’s going to be fixed in two weeks, and your project is going to go live in a week, what do you do?

  • Why do many big companies build their own wheels instead of using other companies’ wheels? In order to control their own business and not be led by others.

2. In order not to be mediocre

  • Everyone is writing, adding, deleting, revising and checking. What advantage do you have over others? Wouldn’t it be awesome if you could say “everyone in my company is using the UI framework I wrote”? There’s a lot of technical, not business, knowledge involved in building UI wheels, right? Like some algorithms.

3. To create

  • What you’ve been doing for so long for other people, do you do anything for yourself? Self-actuation.

4. Why UI wheels and not other wheels

  • For example, why not write your own React framework, instead write the React UI framework?

The React. FunctionComponent and IconPropps

The wheels using the React + TypeScript to write, so how to declare in the ts function components and level Icon passing parameters? The answer is to use the static method react. FunctionComponent provided by React and TypeScript’s interface definition.

// lib/icon.tsx

import React from 'react'

interface IconProps {
  name: string
}

const Icon: React.FunctionComponent<IconProps> = () => {
  return (
    <span>icon</span>
  )
}

export default Icon
Copy the code

Call in index.txt:

import React from "react";
import ReactDOM from "react-dom";
import Icon from './icon'
  
ReactDOM.render(<div>
  <Icon name='wechat'/>
</div>, document.body)
Copy the code

For the above definition, the rear wheel will be used a lot, so don’t worry about it.

Load SVG using svG-sprite-loader

We specified the name of the Icon as wechat, so how to make it display the wechat Icon? First download the corresponding SVG from Ali’s Iconfont

How do I display SVG next? Here we use an SVG-Sprite-loader library and add it to rules under the corresponding Webpack:

{
  test: /\.svg$/,
  loader: 'svg-sprite-loader'
}
Copy the code

Reference in Icon, which of course is also configured for tsconfig.json (this is not the focus of this article):

import React from 'react'
import wechat from './icons/wechat.svg'

console.log(wechat)
interface IconProps {
  name: string
}

const Icon: React.FunctionComponent<IconProps> = () => {
  return (
    <span>
      <svg>
        <use xlinkHref="#wechat"></use>
      </svg>
    </span>
  )
}

export default Icon
Copy the code

Operation effect:

Of course, SVG cannot be written to death directly, we need to specify the corresponding image according to the external name:

// 部分代码
import  './icons/wechat.svg'
import './icons/alipay.svg'

const Icon: React.FunctionComponent<IconProps> = (props) => {
  return (
    <span>
      <svg>
        <use xlinkHref={`#${props.name}`}></use>
      </svg>
    </span>
  )
}
Copy the code

External calls:

ReactDOM.render(<div>
  <Icon name='wechat'/>
  <Icon name='alipay'/>
</div>, document.getElementById('root'))
Copy the code

Operation effect:

importAll

Have you noticed which SVG I need to use? I need to import the corresponding SVG in the corresponding icon component. So if I need 100 SVG, I have to import 100 times.

So we need a way to dynamically import all SVG:

 // lib/importIcons.js
let importAll = (requireContext) => requireContext.keys().forEach(requireContext)
try {
  importAll(require.context('./icons/', true, /\.svg$/))
} catch (error) {
  console.log(error)
}
Copy the code

If you want to understand the code of the appeal, you may need a little node.js foundation, which suggests that you save it directly, next time useful, directly copy over to use the line.

Import ‘./importIcons’ in the Icon component

The use of the React. MouseEventHandler

When we need to register an event for the Icon, we will get an error if we write the onClick event directly on the component, because it does not declare to receive the onClick event type, so we need to declare it, as follows:

/lib/icon.tsx

import React from 'react'
import './importIcons'
import './icon.scss';
interface IconProps {
  name: string,
  onClick: React.MouseEventHandler<SVGElement>
}

const Icon: React.FunctionComponent<IconProps> = (props) => {
  return (
    <span>
      <svg onClick={ props.onClick}>
        <use xlinkHref={`#${props.name}`} />
      </svg>
    </span>
  )
}

export default Icon
Copy the code

Call as follows:

import React from "react";
import ReactDOM from "react-dom";
import Icon from './icon'

const fn: React.MouseEventHandler = (e) => {
  console.log(e.target);
};


ReactDOM.render(<div>
  <Icon name='wechat' onClick={fn}/>
</div>, document.getElementById('root'))
Copy the code

Make Icon respond to all events

Above we only listen for onClick events, but not for other events, so we need to improve. We can’t add event types one by one, we need a unified event type, so what is this?

React gives us an SVGAttributes class, which we need to inherit:

/lib/icon.tsx
import React from 'react'
import './importIcons'
import './icon.scss';
interface IconProps extends React.SVGAttributes<SVGElement> {
  name: string;
}

const Icon: React.FunctionComponent<IconProps> = (props) => {
  return (
    <span>
      <svg 
        onClick={ props.onClick}
        onMouseEnter = {props.onMouseEnter}
        onMouseLeave = {props.onMouseLeave}
      >
        <use xlinkHref={`#${props.name}`} />
      </svg>
    </span>
  )
}

export default Icon
Copy the code

Call method:

import React from "react";
import ReactDOM from "react-dom";
import Icon from './icon'

const fn: React.MouseEventHandler = (e) => {
  console.log(e.target);
};


ReactDOM.render(<div>
  <Icon name='wechat' 
    onClick={fn}
    onMouseEnter = { () => console.log('enter')}
    onMouseLeave = { () => console.log('leave')}
  />
</div>, document.getElementById('root'))
Copy the code

The above will still have problems, we also have onFocus, onBlur, onChange and other events, it is not possible to pass in one by one, so what method is there?

In icon. TSX we can see that we are using props. A clever friend might immediately think of the form {… Props}, rewrite as follows:

. const Icon: React.FunctionComponent<IconProps> = (props) => { return ( <span> <svg className="fui-icon" {... props}> <use xlinkHref={`#${props.name}`} /> </svg> </span> ) } ...Copy the code

React: if the user passes a className to the user, then you know that Vue is really good. It merges the className with the user’s className. React overwrites the user’s className.

...
const Icon: React.FunctionComponent<IconProps> = (props) => {
  const { className, ...restProps} = props
  return (
    <span>
      <svg className={`fui-icon ${className}`} {...restProps}>
        <use xlinkHref={`#${props.name}`} />
      </svg>
    </span>
  )
}
...
Copy the code

If there is no className outside, then there will be a undefined inside

You might be smart enough to use the trinary operator to make a judgment, such as:

className={`fui-icon ${className ? className : ''}`}
Copy the code

But what if there are multiple arguments in this case?

So someone was smart enough to write an inventory of Classnames, how popular it is, more than 3 million downloads a week, and what it does is it handles classnames.

Of course, we only do the simple processing, as shown below

// helpers/classes function classes(... names:(string | undefined )[]) { return names.join(' ') } export default classesCopy the code

Usage:

...
const Icon: React.FunctionComponent<IconProps> = (props) => {
  const { className, name,...restProps} = props
  return (
    <span>
      <svg className={classes('fui-icon', className)} {...restProps}>
        <use xlinkHref={`#${name}`} />
      </svg>
    </span>
  )
}
...
    
    
Copy the code

This will still render the className with an extra space. As a perfector, you don’t want Spaces, so you need to handle Spaces further, using the filters method for arrays in ES6.

// helpers/classes function classes(... names:(string | undefined )[]) { return names.filter(Boolean).join(' ') } export default classesCopy the code

Unit testing

First, we’ll unit test our classes method using Jest, which is recommended by React.

Classes tests use the following example:

import classes from '.. /classes' describe('classes', () => {it(' className', () => {const result = classes('a') expect(result).toequal ('a')}) it(' accept 2 classnames ', ()=>{const result = classes('a', 'b') expect(result).toequal ('a b')}) it(' undefined', ()=>{const result = classes('a', undefined) expect(result).toequal ('a')}) it(' accept all kinds of queer values ', ()=>{const result = classes('a', undefined, 'c ', false, null) expect(result).toequal ('a ')}) it(' accept 0 arguments ', ()=>{ const result = classes() expect(result).toEqual('') }) })Copy the code

Test the UI with Snapshot

Use an Enzyme library to test the UI. The Enzyme is a JavaScript test tool for Determining, manipulating, and iterating the React Components output. The Enzyme API makes DOM manipulation and traversal flexible and intuitive by mimicking jQuery’s API. Enzyme compatible with all major test runners and judgment libraries.

Icon’s test case

import * as renderer from 'react-test-renderer' import React from 'react' import Icon from '.. /icon' import {mount} from 'enzyme' describe('icon', () => { it('render successfully', () => { const json = renderer.create(<Icon name="alipay"/>).toJSON() expect(json).toMatchSnapshot() }) it('onClick', () => { const fn = jest.fn() const component = mount(<Icon name="alipay" onClick={fn}/>) component.find('svg').simulate('click') expect(fn).toBeCalled() }) })Copy the code

What if THE IDE prompt cannot find Describe and IT?

Solutions:

  1. yarn add -D @types/jest
  2. Import ‘jest’ at the beginning of the file

This is because describe and IT’s type declaration files are located in Jest, which you can view by holding Down CTRL and clicking jest.

If not, you need to set up a reference to jest in WebStorm:

This is because typescript excludes type declarations in node_modules by default.

conclusion

The above is mainly summarized in the process of learning to build wheels, the environment is not detailed, mainly record the realization of Icon wheel some ideas and matters needing attention, want to see the source code, run to see, you can click here to view.

reference

Teacher Fang Yinghang’s React Wheels course

communication

This article is updated every week, you can search wechat “big move the world” for the first time to read and urge more (one or two earlier than the blog hey), this article GitHub github.com/qq449245884… It has been included and sorted out a lot of my documents. Welcome Star and perfect. You can refer to the examination points for review in the interview.