美文网首页
icon组件

icon组件

作者: sweetBoy_9126 | 来源:发表于2019-10-28 16:33 被阅读0次

    创建一个icon.tsx接受一个name属性,也就是你的图标的名字

    • icon.tsx
    import React from 'react';
    interface IconProps {
        name: string
    }
    
    const Icon: React.FunctionComponent<IconProps> = (props) => {
        return (
            <span>{props.name}</span>
        )
    }
    export default Icon;
    
    • index.tsx
    import Icon from './icon'
    ReactDom.render(
        <div>
           <Icon name="wechat"/>
        </div>
        , document.querySelector('#root'));
    

    通过iconfont引入一个svg

    1. 在webpack中添加svg的loader
    {
      test: /\.svg$/,
      loader: 'svg-sprite-loader'
    }
    
    1. 在我们的ts的声明中声明一下svg的类型
      创建lib/types/custom.d.ts
    declare module '*.svg' {
        const content: any;
        export default content;
    }
    
    1. 在tsconfig里添加types
    • tsconfig.json
    "include": [
      "lib/**/*"
    ],
    
    1. 使用svg标签,里面是一个use它的属性是xlinkHref定义的id就是我们svg的文件名
    import './icons/wechat.svg'
    const Icon: React.FunctionComponent<IconProps> = (props) => {
        return (
            <span>
                <svg>
                    <use xlinkHref="#wechat"></use>
                </svg>
            </span>
        )
    }
    
    1. 通过props动态接受多个name
    import './icons/wechat.svg'
    import './icons/alipay.svg'
    import './icons/qq.svg'
    interface IconProps {
        name: string
    }
    
    const Icon: React.FunctionComponent<IconProps> = (props) => {
        return (
            <span>
                <svg>
                    <use xlinkHref={`#${props.name}`}/>
                </svg>
            </span>
        )
    }
    export default Icon; 
    

    问题:如果我们有几十个icon的话,那么我们一个个import就会很麻烦,我们如何直接import一个目录,让它直接自动引入下面的所有文件那。
    解决方法:新建一个lib/importicons.js

    let importAll = (requireContext) => requireContext.keys().forEach(requireContext)
    try {
      importAll(require.context('./icons/', true, /\.svg$/))
    } catch {
      
    }
    

    tree-shaking

    • 静态引入
      对于目录或者库里的某一模块或某一文件引入,比如:
    import A from './a
    
    • 非静态引入
      直接引入一个完整的库或者完整的目录
    importAll './all'
    

    tree-shaking: 比喻我们项目的依赖把那些没有依赖的从打包里删掉,只留下我们真正用到的依赖,tree-shaking的基础是静态引入

    配置sass

    1. 配置scss loader
    • webpack.config.js
    rules: [
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'sass-loader']
      }
    ]
    

    loader的解析执行顺序从右往左
    sass-loader: 将.scss文件以字符串的形式把语法翻译成css的语法;
    css-loader: 将翻译的css语法文件变成一个对象;
    style-loader: 把对象变成一个style标签

    yarn add --dev style-loader css-loader sass-loader
    yarn add node-sass
    

    这里为了不影响其他人的类名和被其他人影响,我们最后在我们的类名前面加一个前缀

    • icon.scss
    .ireact-icon {
      width: 1.4rem;
      height: 1.4rem;
    }
    
    • icon.tsx
    <svg className="ireact-icon">
        <use xlinkHref={`#${props.name}`}/>
    </svg>
    

    接受一个onClick事件

    -i ndex.tsx

    let fn = (e: React.MouseEvent) => {
        console.log((e.target as HTMLDivElement).style)
    }
    ReactDom.render(
        <div>
           <Icon name="qq" onClick={fn}/>
        </div>
        , document.querySelector('#root'));
    
    • icon.tsx
    interface IconProps {
        name: string;
        onClick: React.MouseEventHandler //onClick类型是一个React的鼠标回调事件
    }
    
    const Icon: React.FunctionComponent<IconProps> = (props) => {
        return (
            <svg className="ireact-icon" onClick={props.onClick}>
                <use xlinkHref={`#${props.name}`}/>
            </svg>
        )
    }
    

    问题:如果我们有多个事件的话,那我们就得每个都在我们我们的iconProps里定义,就会很复杂
    解决办法:让IconProps继承React里的SVGAttributes它里面有所有的事件和属性

    • icon.tsx
    interface IconProps extends React.SVGAttributes<SVGElement>{
        name: string;
    }
    const Icon: React.FunctionComponent<IconProps> = (props) => {
        return (
            <svg className="ireact-icon" onClick={props.onClick} onMouseEnter={props.onMouseEnter}>
                <use xlinkHref={`#${props.name}`}/>
            </svg>
        )
    }
    
    • index.tsx
                 onClick={fn}
                 onMouseEnter={() => console.log('mousenter')}
                 onMouseLeave={() => console.log('mouseleave')}
           />
    

    上面我们在子组件中调用父组件的事件的时候,因为我们也是不确定有哪些事件,所以我们也可以通过...props把所有的事件都解构到icon组件上

    <svg className="ireact-icon" {...props}>
        <use xlinkHref={`#${props.name}`}/>
    </svg>
    

    问题1:因为我们是直接通过props把所有的属性放到了svg上,这时如果我们使用icon的时候父组件也传入一个className,这个className也会在我们解构的props里,所以我们自己的className就会被覆盖
    比如:

    <Icon name="qq" className="qqq">
    
    const Icon: React.FunctionComponent<IconProps> = (props) => {
        const { className, ...restProps } = props
        return (
            <svg className={`ireact-icon ${className}`} {...restProps}>
                <use xlinkHref={`#${props.name}`}/>
            </svg>
        )
    }
    

    问题2:如果用户没有传className那么就会在页面显示一个undefined
    解决方法:
    1). 通过三元运算符判断是否有className,如果有就用否则就是空

    <svg className={`ireact-icon ${className ? className : ''}`} {...restProps}>
    

    2). 引入一个classNames库,可以把我们多个class合起来,并且不会出现undefined,手写一个classNames

    • classes.tsx
    // 将数组的所有参数解构出来,里面的每一项是string或undefined
    function classes(...names: (string | undefined)[]) {
        // 将每一项的值通过Boolean值返回,undefined会被过滤
        return names.filter(Boolean).join(' ')
    }
    export default classes
    
    • icon.tsx
    import classNames from './helpers/classes'
    <svg className={classNames('ireact-icon', 'qq', className)} {...restProps}>
    

    进一步通过解构来简化我们的参数

    const Icon: React.FunctionComponent<IconProps> = ({
            className,
            name,
            ...restProps
        }) => {
        return (
            <svg className={classNames('ireact-icon', 'qq', className)} {...restProps}>
                <use xlinkHref={`#${name}`}/>
            </svg>
        );
    };
    

    单元测试

    最简单的classes单元测试

    import classes from '../classes'
    describe('classes', () => {
        it('接受 1 个className', () => {
            const result = classes('a')
            expect(result).toEqual('a')
        })
        it('接受 2 个className', () => {
            const result = classes('a', 'b')
            expect(result).toEqual('a b')
        })
        it('接受 undefined 结果不会出现 undefined', () => {
            const result = classes('a', 'b', undefined)
            expect(result).toEqual('a b')
        })
        it('接受 0 个参数', () => {
            const result = classes()
            expect(result).toEqual('')
        })
    })
    
    Snapshot(快照)

    当我们运行测试的时候会得到第一个快照,这时候我们需要判断这个快照是对的还是错的,如果是对的就通过test -u 保存下来,如果是错的那么我们就继续运行测试,每次就是跟上一次运行的结果作对比。

    • icon.unit.jsx
    import React from 'react'
    import renderer from 'react-test-renderer'
    import Icon from '../icon'
    
    describe('Icon', () => {
      it('是个svg', () => {
        //渲染一个Button,因为Button是一个对象所以我们可以把它转成json
        const json = renderer.create(<Icon/>).toJSON()
        //期待它去匹配Snapshot
        expect(json).toMatchSnapshot()
      })
    })
    

    运行yarn test报错 Cannot find module 'babel-preset-react-app'
    安装babel-preset-react-app发现错误又变了


    解决方法:
    1). 在test下面新建一个mocks目录,里面存放我们默认mock的文件数据和对象数据
    • mock/file-mock.js
    // 导出的字符串可以随便写
    module.exports = 'test-file-stub'
    
    • mock/object-mock.js
    module.exports = {}
    

    2). 配置jest.config.js里的moduleNameMapper
    让css less sass使用object-mock随便导出一个对象其他的使用file-mock随便导出一个字符串

    moduleNameMapper: {
      "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/__mocks__/file-mock.js",
      "\\.(css|less|sass|scss)$": "<rootDir>/test/__mocks__/object-mock.js",
    },
    

    这时候我们运行yarn test在我们的快照中会生成

    如果我们对我们的测试进行的更改,比如传入一个name,那么就需要运行yarn test -u 来更新我们的快照为最新的

    测试点击事件

    使用enzyme库来测试点击事件

    1. yarn add --dev enzyme
    2. 将要测试的元素通过mount挂载到页面
    import { mount } from 'enzyme'
    const component = mount(<Icon name="alipay" onClick={fn}/>)
    
    1. 通过simulate来触发对应事件
    component.find('svg').simulate('click')
    
    1. 声明触发事件的函数为jest.fn()
    const fn = jest.fn()
    
    1. 期待我们的函数被调用
    expect(fn).toBeCalled
    
    1. 在test/setupTests.js里添加enzyme配置
    const enzyme = require('enzyme')
    const Adapter = require('enzyme-adapter-react-16')
    
    enzyme.configure({adapter: new Adapter()})
    
    1. yarn add --dev enzyme-adapter-react-16

    完整点击事件测试代码

    it('onClick', () => {
      const fn = jest.fn()
      const component = mount(<Icon name="alipay" onClick={fn}/>)
      component.find('svg').simulate('click')
      expect(fn).toBeCalled
    })
    

    相关文章

      网友评论

          本文标题:icon组件

          本文链接:https://www.haomeiwen.com/subject/qfkbvctx.html