dva react中截取字符串_react中 安装dva不使用dva cli - CSDN
  • { key: '/stock/test', name: '测试React' } 2. 左侧Menu 已经出现,增加路由: { path: '/stock/test', component: require('./TestKn'), },3.在路由目录,新增TestKn.jsx routs / TestKn.jsx --

    1.左侧Menu 新增 模块


        { key: '/stock/test', name: '测试React' }

    2. 左侧Menu 已经出现,增加路由:

      {
        path: '/stock/test',
        component: require('./TestKn'),
      },
    3.在路由目录,新增TestKn.jsx
    routs / TestKn.jsx  --万物之本


    import React, { PropTypes } from 'react';
    import { connect } from 'dva';
    import Search from '../../components/Inventory/TestKn/search';
    
    const TestKn = ({ stockInData, dispatch }) => {
    const TestProps={
      testId:5,
      testAlert(value){
        alert("此弹框是源自TestKn:"+value);
      }
    }
      return (
        <div >
      <Search {...TestProps} />
        </div>
      );
    };
    TestKn.propTypes = {
      dispatch: PropTypes.func,
    };
    function mapStateToProps(stockInData) {
      return { stockInData };
    }
    
    export default connect(mapStateToProps)(TestKn);
    

    TestProps, 父组件给子组件传值

    4.在components  组件文件夹内的Inventory 新增TestKn文件夹,里面增加search.jsx

    import React, { PropTypes } from 'react';
    import { Form, Input, Button, Select, Breadcrumb, DatePicker, Row, Col, Radio } from 'antd';
    
    
    const search=({
      testId,
      testAlert
    })=>{
      return(
        <div>
          <Button onClick={()=>testAlert(testId)}>按此获取父组件TestKn内的testId值</Button>
        </div>
      );
    }
    search.propTypes = {
      testId:PropTypes.string,
      testAlert:PropTypes.func,
    };
    export default Form.create()(search);
    

    此时,点击测试按钮,即可弹出父组件给传 的值。

        =>    此弹框是源自TestKn:5

    5. 新增models

    在models 文件夹内 新建testKn.js

    import { parse } from 'qs';
    import { message } from 'antd';
    
    export default {
      namespace: 'testKn',
      state:{
        testId:0,
      },
      subscriptions: {
        setup({ dispatch, history }) {
          history.listen((location) => {
            if (location.pathname === '/stock/test') {
              //初始化testId的值为10
              dispatch({
                type: 'update',
                payload: { ...location.query, testId: 10 },
              });
            }
          });
        },
      },
      effects: {
        * update({ payload }, { call, put }) {
    
            yield put({ type: 'showModal', payload });
    
      },
        },
      reducers: {
        update(state, action) {
          return { ...state, ...action.payload };
        },
          }
    }
    

    并注册此models 在enity的inventory.js中

    app.model(require('../models/inventory/testKn'));

    6.在search.jsx中新增按钮:

     

     <Button onClick={()=>testAlertByStore()}>按此获取store内testId值</Button>

    更新 routes.jsx中的TestKn.jsx

    import React, { PropTypes } from 'react';
    import { connect } from 'dva';
    import Search from '../../components/Inventory/TestKn/search';
    
    const TestKn = ({
      dispatch,
      storeInfo,
     }) => {
      console.log(storeInfo);
    const TestProps={
      testId:5,
      testAlert(value){
        alert("此弹框是源自TestKn:"+value);
      },
      testAlertByStore(){
          alert("此弹框内容是源自Store:"+storeInfo.testId);
      }
    
    }
      return (
        <div >
      <Search {...TestProps} />
        </div>
      );
    };
    TestKn.propTypes = {
      dispatch: PropTypes.func,
    };
    function mapStateToProps(stockInData) {
      return {
        storeInfo:stockInData.testKn,
      };
    }
    
    export default connect(mapStateToProps)(TestKn);


    此时,弹框即可弹出sotre中的 10

    => 此弹框内容是源自Store:10


    7. 增加按钮,更改store中值

    search.jsx

     <Button onClick={()=>testUpdateStore(20)}>按此更改store内testId值为20</Button>
    

    TestKn.jsx

      testUpdateStore(value){
        dispatch({
        type: 'testKn/update',
        payload: { testId: value },
      });







    展开全文
  • 1-1、在BasicLayout做导航守卫,先判断浏览器环境,微信内的话,判断本地是否存在openId,有的话校验openId是否有效,有效的话缓存所需数据,无效的话跳转登录界面重新登陆,本地没有openId的话,判断路径是否含...

    1、微信授权

    1-1、在BasicLayout做导航守卫,先判断浏览器环境,微信内的话,判断本地是否存在openId,有的话校验openId是否有效,有效的话缓存所需数据,无效的话跳转登录界面重新登陆,本地没有openId的话,判断路径中是否含code(第一次进入授权页面href中不带code,请求微信接口跳转页面路径中存在code,即第二次进入路径存在code),没有的话去授权页面

    
    import './index.css';
    import React, { PureComponent } from 'react';
    import MenuBar from '@/components/MenuBar';
    import { connect } from 'dva';
    import withRouter from 'umi/withRouter';
    import router from 'umi/router';
    import { getToken, setAuthority,
      setToken,
      setCurrentUser} from '../utils/authority'
    import { checkOpenId} from '@/services/login';
    import '@/layouts/index.css';
    // 底部有bar菜单
    const BarRoutes = ['/', '/activity', '/ranking', '/user'];
    class BasicLayout extends PureComponent {
      getQueryString(name) {
        var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
        var r = window.location.search.substr(1).match(reg);
        if (r != null) {
            return unescape(r[2]);
        }
        return null;
    }
      componentWillMount() {
        const { children, location, loading, route } = this.props;
        //判断是否为微信环境
        var ua = window.navigator.userAgent.toLowerCase();
        if (ua.match(/MicroMessenger/i) == 'micromessenger') {
          //console.log("微信")
          if (localStorage.getItem('openId')) {
            //已授权
            //console.log("已授权");
            let params = { 'openid': localStorage.getItem('openId') }
            //跟后台校验
            checkOpenId(params).then(res => {
              console.log(res);
              if(res.success){
                const { tokenType, accessToken, authority, account, userName, avatar, userid,companyid} = res.data
                const token = `${tokenType} ${accessToken}`;
                setToken(token);
                setAuthority(authority);
                setCurrentUser({ avatar, account, name: userName, authority, userid });
                localStorage.setItem('userid', userid);
                localStorage.setItem('companyid',companyid)
              }else{
                  if(location.pathname=='/login'){
                    return ;
                  }else{
                    router.replace('/login');
                  } 
              }
            }).catch(err => {
    
            })
    
          } else {
            //console.log("未授权")
            if(this.getQueryString("code")){
              return;
            }else{
              localStorage.setItem('firstLink',window.location.href)
              router.replace('/author')
            }
          }
    
        } else {
          //浏览器
          console.log("浏览器")
          //next() 
          const token = getToken();
          if (token) {
    
          } else {
            router.replace('/login')
          }
    
        }
      }
      componentWillUnmount(){
        localStorage.setItem('lastLink',window.location.href)
      }
      render() {
        const { children, location, loading, route } = this.props;
        //去除tabbar的其他路由
        if (BarRoutes.indexOf(location.pathname) < 0) {
          // console.log(route.routes)
          // let title=CheckTitle(location,route.routes)
          return (
            <div style={{ overflowX: 'hidden', height: "100%" }}>
              {children}
            </div>
          )
        }
        //Tabbar路由
        return (
          <div style={{ overflowX: 'hidden', height: "100%" }}>
            <MenuBar pathname={location.pathname}>{children}</MenuBar>
          </div>
        );
      }
    }
    
    export default withRouter(connect(({ app, loading }) => ({ app, loading }))(BasicLayout));
    

    1-2、授权页面(以空白页进行静默授权)

    
    import React, { PureComponent } from 'react';
    import { connect } from 'dva';
    import { getOpenId} from '@/services/login';
    import router from 'umi/router';
    import styles from './index.less';
    @connect(({ login, loading }) => ({
        login,
        loading,
    }))
    class Author extends PureComponent {
        constructor(props) {
            super(props);
            this.state = {
            }
        }
        getQueryString(name) {
            var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
            var r = window.location.search.substr(1).match(reg);
            if (r != null) {
                return unescape(r[2]);
            }
            return null;
        }
        // 截取code
        GetUrlParame(parameName) {
            /// 获取地址栏指定参数的值
            /// <param name="parameName">参数名</param>
            // 获取url中跟在问号后面的部分
            var parames = window.location.search
            // 检测参数是否存在
            if (parames.indexOf(parameName) > -1) {
                var parameValue = ''
                parameValue = parames.substring(parames.indexOf(parameName), parames.length)
                // 检测后面是否还有参数
                if (parameValue.indexOf('&') > -1) {
                    // 去除后面多余的参数, 得到最终 parameName=parameValue 形式的值
                    parameValue = parameValue.substring(0, parameValue.indexOf('&'))
                    // 去掉参数名, 得到最终纯值字符串
                    parameValue = parameValue.replace(parameName + '=', '')
                    return parameValue
                }
                return ''
            }
        }
        componentWillMount() {
            //localStorage.setItem('firstLink',window.location.href)
            if (!this.getQueryString("code") && !localStorage.getItem('code')) {
                let url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx49072aeabf0d54bd&redirect_uri=${encodeURIComponent(window.location.href)}&response_type=code&scope=snsapi_base&state=1&connect_redirect=1#wechat_redirect`
                window.location.href = url;
                //console.log("第一次进入")
            } else if (!localStorage.getItem('code')) {
                //console.log('第二次进入且路径中存在code')
                localStorage.setItem('code', this.GetUrlParame("code"));
                //后台请求微信返回openiD成功跳转登录界面
                let params = { 'wxcode': localStorage.getItem('code') }
                getOpenId(params).then(res=>{
                    //console.log(res)
                    localStorage.setItem('openId',res.data.openid);
                    router.replace('/login')
                }).catch(err=>{
                    console.log(err)
                })
            } else {
                //console.log("2次以上进入")
            }
        }
        render() {
            return (
                <div className={styles.author}>
                </div>
            );
        }
    }
    export default Author;
    

    2、微信分享

    2-1、安装weixin-js-sdk

    yarn add weixin-js-sdk

    2-2、 新建wxshare.js

    import {wxShare} from '../services/wxshare'
    import wx from 'weixin-js-sdk';
    //
    		//要用到微信API								
    function getJSSDK(shareUrl,dataForWeixin) {
      let params={'shareUrl':shareUrl}
      wxShare(params).then(res => {
        //console.log('22222',res,dataForWeixin);
        //console.log(typeof(res));
        // let response=JSON.parse(res.data);
        // console.log(response)
        wx.config({
          debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
          appId: res.data.appId, // 必填,公众号的唯一标识
          timestamp: res.data.timestamp, // 必填,生成签名的时间戳
          nonceStr: res.data.noncestr, // 必填,生成签名的随机串
          signature: res.data.signature, // 必填,签名
          jsApiList:[
            'onMenuShareTimeline', 
            'onMenuShareAppMessage'
          ] 
        })
        wx.ready(function () {
            //分享给微信朋友
          wx.onMenuShareAppMessage({
            title: dataForWeixin.title,
            desc: dataForWeixin.des,
            link: dataForWeixin.linkurl,
            imgUrl: dataForWeixin.img,
            success: function success(res) {
              console.log(res,'已分享');
            },
            cancel: function cancel(res) {
              console.log('已取消');
            },
            fail: function fail(res) {
              //alert(JSON.stringify(res));
            }
          });
          // 2.2 监听“分享到朋友圈”按钮点击、自定义分享内容及分享结果接口
          wx.onMenuShareTimeline({
            title: dataForWeixin.title,
            link: dataForWeixin.linkurl,
            imgUrl: dataForWeixin.img,
            success: function success(res) {
              //alert('已分享');
            },
            cancel: function cancel(res) {
              //alert('已取消');
            },
            fail: function fail(res) {
              //alert(JSON.stringify(res));
            }
          });
    	// 2.3 监听“分享到QQ”按钮点击、自定义分享内容及分享结果接口
        //   wx.onMenuShareQQ({
        //     title: dataForWeixin.title,
        //     desc: dataForWeixin.desc,
        //     link: dataForWeixin.linkurl,
        //     imgUrl: dataForWeixin.img,
        //     trigger: function trigger(res) {
        //       //alert('用户点击分享到QQ');
        //     },
        //     complete: function complete(res) {
        //       alert(JSON.stringify(res));
        //     },
        //     success: function success(res) {
        //       //alert('已分享');
        //     },
        //     cancel: function cancel(res) {
        //       //alert('已取消');
        //     },
        //     fail: function fail(res) {
        //       //alert(JSON.stringify(res));
        //     }
        //   });
          // 2.4 监听“分享到微博”按钮点击、自定义分享内容及分享结果接口
        //   wx.onMenuShareWeibo({
        //     title: dataForWeixin.title,
        //     desc: dataForWeixin.desc,
        //     link: dataForWeixin.linkurl,
        //     imgUrl: dataForWeixin.img,
        //     trigger: function trigger(res) {
        //       //alert('用户点击分享到微博');
        //     },
        //     complete: function complete(res) {
        //       // alert(JSON.stringify(res));
        //     },
        //     success: function success(res) {
        //       //alert('已分享');
        //     },
        //     cancel: function cancel(res) {
        //       // alert('已取消');
        //     },
        //     fail: function fail(res) {
        //     // alert(JSON.stringify(res));
        //     }
        //   });
        })
        wx.error(function (res) {
          //alert("微信验证失败");
        });
      })
    }
    export default {
      // 获取JSSDK
      getJSSDK
    }

    2-3、在组件生命周期上挂在,IOS验签失败,重新加载验签成功

    import sdk from "../../../utils/wxshare"
    
     componentDidMount() {
        this.setState({ animating: true })
        var ua = window.navigator.userAgent.toLowerCase();
        if (ua.match(/MicroMessenger/i) == 'micromessenger') {
          var u = navigator.userAgent, app = navigator.appVersion;
          var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
          if (isIOS) {
            if (window.location.href.indexOf("#reloaded") == -1) {
              window.location.href = window.location.href + "#reloaded";
              window.location.reload();
            }
          }
        }
        const { dispatch, location } = this.props;
        //请求详情数据
        dispatch({
          type: 'activity/fetchDetail',
          payload: {
            id: this.props.match.params.id,
            userid: localStorage.getItem('userid')
          },
          callback: res => {
            //console.log(res);
            let obj = {
              title: res.data.pgname,
              des: res.data.pgdesc,
              linkurl: window.location.href,
              img: `${res.data.pgpic}`,
            }
            let url = encodeURIComponent(window.location.href.split('#')[0]);
            sdk.getJSSDK(url, obj);
            this.setState({
              pgStartTime: res.data.pgstarttime,
              pgEndTime: res.data.pgendtime,
              animating: false
            },()=>{
              this._initScroll()
            })
          }
        });
    }

     

    展开全文
  • 写在前面 欢迎关注公众号:java开发高级进阶, 有不会的可以直接...环境:Nodejs + React + Dva + monaco-editor + react-monaco-editor + antd 一、绑定快捷键 调用editor.addCommand方法绑定快捷键,通过monaco....

    写在前面 欢迎关注公众号:java开发高级进阶, 有不会的可以直接公众号留言提问

    monaco-editor不提供全局搜索,只提供单个文件内的搜索,那么如何实现全局搜索呢?

    环境:Nodejs + React + Dva + monaco-editor + react-monaco-editor + antd

    一、绑定快捷键

    调用editor.addCommand方法绑定快捷键,通过monaco.KeyMod和monaco.KeyCode选择快捷键

    快捷键要在编辑器创建完成时创建,故这段代码要写在editorDidMount里面

    全局搜索要有一个搜索框供输入关键字,故在触发快捷键的时候让搜索框显示,默认是不显示。代码如下:

    editorDidMount = (editor, monaco) => {
    	editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KEY_F, () => {
    		// 显现全局搜索框
    		this.setState({
    			showModel: true
    		})
    	});
    
    	editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KEY_N, () => {
    		// 显现文件搜索框
    		this.setState({
    			showFileModel: true
    		})
    	});
    	editor.focus();
    }
    

    二、搜索框实现

    搜索框样式主要看自己的需求,我这里只写个简单的搜索框

    如何实现搜索框根据内容多少滚动显示列表是我们要考虑的一个问题,经过一番搜索,我锁定了react-infinite-scroller,通过InfiniteScroll API实现滚动下拉效果。代码如下:

    <div style={{height:'400px', overflow:'auto'}}>
    	<InfiniteScroll
    			pageStart={0}
    			loadMore={true}
    			hasMore={true || false}
    			loader={null}
    			useWindow={false}
    	>
    		{rowsList}
    	</InfiniteScroll>
    </div>
    

    三、关键字搜索功能

    monaco-editor不提供全局搜索功能,如果要在monaco-editor基础上实现全局搜索,我们可以借助单个文件的搜索功能,然后遍历业务系统的所有文件,对每个文件的搜索结果进行汇总实现全局搜索。

    这里需要考虑几个问题:

    (1) monaco-editor单个文件搜索是基于创建model的基础上,而通过编辑器点击打开某个文件时才会创建model,我们怎么能获取业务系统所有文件的model?
    (2) 业务系统有.git,node_modules等不需要遍历的文件,还有文件夹里的子文件夹及子文件,那么如何实现遍历?

    首先针对第一个问题做出解答:

    如果对monaco-editor没有经过一番研究可能不会发现这个规律,通过查看官网的api,发现monaco-editor提供createModel方法,我们可以手动创建model

    针对第二个问题做出解答

    其实.git ,node_modules文件很好排除,只要遍历的时候加个if判断就行;

    遍历到文件夹时,我们可以通过nodejs提供的fs.Stats类里面的isDirectory()方法判断,如果是文件夹,就进入文件夹里继续遍历,这里用到递归遍历

    在单个文件中搜索关键字可以使用model.findMatches(keyword)实现

    再建一个全局的变量用于保存遍历到的结果就行。

    代码如下:

    let result =  new Map();
    
    /**
     * 业务系统文件遍历
     * @param projectPath
     * @param searchText
     */
    getProjectFileListInfo = (projectPath, searchText) => {
    
    	let pa = window.fs.readdirSync(projectPath);
    	let that = this;
    	pa.forEach(function(ele,index){
    		let info = window.fs.statSync(projectPath+"/"+ele);
    		if(ele == 'node_modules' || ele == '.git' ) {
    
    		} else if(info.isDirectory()){
    			that.getProjectFileListInfo(projectPath + "/"+ ele , searchText)
    
    		}else{
    			// 读取文件内容
    			const fileInfo = window.fs.readFileSync(projectPath+'/'+ele, 'utf-8');
    			// 根据文件名后缀判断应该创建哪种model
    			// 分离后缀
    			let suffix = ele.split('.')[1];
    			switch (suffix) {
    				case 'js':
    
    					let tempModel1 = monaco.editor.createModel(fileInfo, 'javascript');
    					that.findMatches(tempModel1, searchText, projectPath + "/"+ ele, ele);
    					break;
    				case 'html':
    
    					let tempModel2 = monaco.editor.createModel(fileInfo, 'html');
    					that.findMatches(tempModel2, searchText, projectPath + "/"+ ele, ele);
    					break;
    				case 'json':
    
    					let tempModel3 = monaco.editor.createModel(fileInfo, 'javascript');
    					that.findMatches(tempModel3, searchText, projectPath + "/"+ ele, ele);
    					break;
    				case 'java':
    
    					let tempModel4 = monaco.editor.createModel(fileInfo, 'java');
    					that.findMatches(tempModel4, searchText, projectPath + "/"+ ele, ele);
    					break;
    			}
    
    		}
    	})
    
    };
    
    
    /**
     * 根据关键字匹配单个文件
     * @param model
     * @param searchText
     * @param filePath
     * @param fileName
     */
    findMatches = (model, searchText, filePath, fileName) => {
    	let arr = [];
    	for (let match of model.findMatches(searchText)) {
    		arr.push({
    			text:model.getLineContent(match.range.startLineNumber),
    			range: match.range,
    			model: model,
    			filePath: filePath,
    			fileName: fileName
    		});
    	}
    
    	result.set(model.uri.toString(),arr);
    };
    

    四、搜索结果列表展示

    先看效果图

    在这里插入图片描述

    在展示列表中,我们希望看到关键字所在的文件名,行号,甚至是路径,只要我们保存展示结果时将这些信息都保存进去,然后react渲染时渲染出来,代码如上面arr变量所示

    五、关键字高亮显示

    在上图我们可以看到展示结果中所有的登录关键字都是黄色背景显示的,这里调用antd的Typography 实现高亮。

    代码如下:

    // ev为result,全局搜索的结果
    let i=0;
    let rowsList = [];
    ev.forEach((values,key) =>{
    	values.forEach((row) => {
    		i++;
    		let rowReplace = row.text.replace(keyword,'<Text mark>'+keyword+'</Text>');
    		// 关键字字符串截取,添加高亮
    		let preV = row.text.substring(0,row.range.startColumn - 1);
    		let nextV = row.text.substring(row.range.endColumn - 1, row.text.length);
    		rowsList.push(<List.Item className="SelectInfo-listItem" key={i} onClick={() => goto(row.range, row.model, row.filePath, row.fileName)}>
    			<div>{preV}<Text mark>{keyword}</Text>{nextV}</div>
    			<List.Item.Meta/>
    			<div className="SelectInfo-foot">{row.fileName}: {row.range.startLineNumber}</div>
    			</List.Item>);
    	})
    });
    

    从代码中可以看出,我这里使用的是字符串截取找到关键字然后给关键字添加。可以看出我给展示添加了文件名和所在行属性。

    六、goto点击跳转

    实现差不多了,怎么能少掉点击展示列跳转到对应文件即编辑器打开对应文件

    从标题五可以看出,我给每一行加了onClick点击调用goto方法。

    代码如下:

    //这里range和model,对应findAllMatches返回结果集合里面对象的range和model属性
    goto = (range, model, filePath, fileName) => {
    	this.openFileEditor(filePath, fileName);
    	this.setState({
    		showModel: false
    	});
    	this.setState({
    		showFileModel: false
    	})
    
    };
    
    openFileEditor = (file, fileName) => {
    
    	const { dispatch } = this.props;
    	dispatch({
    		type: 'project/openTab',
    		payload: {
    			key: fileName, title: fileName, model: { filePath: file, fileName: fileName }
    		}
    	});
    };
    
    
    /**
     * TAB:{
     *       key:'', 标签页唯一标识
     *       title:'', 标签页标题
     *       isDirty: false, 标识模型数据是否发生变化, 关闭标签页前判断isDirty是否true,如果是,则需要触发保存的方法
     *       model: {
     *       }
     * }
     * @param {*} state 
     * @param {*} action 
     */
    openTab(state, {payload}) {
    	let openTabs = state.openTabs.concat([]);
    	const openFile = {
    		key: payload.key,
    		title: payload.title,
    		extension: payload.extension,
    		model: payload.model
    	};
    	
    	openFile.isDirty = false;
    	const index = openTabs.findIndex(file => file.key == openFile.key);
    	if (index == -1) {
    		openTabs.push(openFile);
    	}
    	return {...state, openTabs, selectTabPane: openFile.key, lastSelectedPane: state.selectTabPane}
    },
    // 通过openTab收集所有打开的文件保存在openTabs里,方便后续的操作
    
    /**
     * 打开的文件
     */
    let openTabs = this.props.openTabs.map(tab => {
    	const title = (tab.isDirty ? "* " : '' ) + tab.title;
    	return <TabPane style={{height: '100%'}} closable={true} tab={title} key={ tab.key }> {pluginInject.openDynamicTab(tab)} </TabPane>
    });
    panels = panels.concat(openTabs);
    
    /**
    通过{
    		panels
    	}将面板显示出来
    */
    
    /**
     * 打开标签页面板
     */
    openDynamicTab(tab) {
    	const { key } = tab;
    	//如果传入的参数没有extension, 则通过key取得
    	const extension = tab.extension ? tab.extension : key.split('.')[1];
    	const editor = this.getEditorPlugin(extension);
    	if (editor) {
    		return this.getEditorPlugin(extension).getTabPanel(tab, extension);
    	}
    	return <div>未找到[{extension}]类型的编辑器</div>
    },
    

    七 全局文件搜索

    文件搜索比关键字搜索简单一点,在遍历业务系统时遍历所有文件,将和关键字匹配的所有文件保存到result中, 其他的操作一样

    结束

    部分源码请到Github上:https://github.com/griabcrh/global-search

    到这里就结束了,恭喜你学会了怎么实现全局搜索,如果觉得本问对你有帮助,欢迎关注公众号:java开发高级进阶,get到跟多知识。
    在这里插入图片描述

    展开全文
  • 算起来有好几个月没记录学习的东西了,自己...2、开始了学习react,并利用 react 开始重写自己的新博客项目了,这次要好好做了,技术栈的话就 react react-router redux express就可以了,这次不打算使用 mongoos...

    算起来有好几个月没记录学习的东西了,自己确实变懒了。。。

    在这期间
    1、看完了 underscore.js 的源码,分析了下公司项目中在vue-cli基础上扩展的一些webpack配置
    2、开始了学习react,并利用 react 开始重写自己的新博客项目了,这次要好好做了,技术栈的话就 react react-router redux express就可以了,这次不打算使用 mongoose ,打算写原生的 mongodb 来好好学习语法,现阶段脚手架及周边工具基本配齐了,脚手架是参考 vue-cli 及 create-react-app 以及看着 webpack3 的文档全手动配置的,目录结构有点参考 dva 的教程的意思,正在做 微信授权 支付宝授权的登录
    3、在做公司项目的过程中还看了 python 的语法,写了个很简单的上传代码后会自动build及覆盖测试服务器上相应打包文件的脚本,也是懒得自己手动搞这些,原理很简单,就是检测svn的版本号来 build 及 cp 到相应目录即可
    4、遇到过一些异步队列的问题,感觉用 promise 解决不够优雅,又详细看了下 js runtime 下的 event loop的详解~发现co包可以解决,但根据源码来看,还是利用 ES6 生成器中的 yeild 和 promise 封装的,相对很优雅的解决方案。

    以下为学习中推荐给大家的一些文档

    webpack3 文档链接
    python 学习链接-廖雪峰
    dva 学习链接
    event loop –micro task macro task 详解

    相信 underscore 与 express、Node 等其他的一些相关链接应该不用我给了吧,大家都可以随便找到~

    underscore.js 的话是强烈建议大家去看看的,尤其是它的 中间函数 与 一些断言 来实现函数式编程的思路,Function 那章的 节流函数(.throttle) 及 延时函数(.delay) 建议一定要看,比我之前写的严谨太多,实现方式是真的精巧,以后有空可能会拉出来分析一下。

    js 运行环境下的任务队列 event loop 也是绝对需要补的基础, 虽然 co 包和 ES6 的 Generator 都解决了这个问题。

    讲解的为vue-cli webpack模板 2017年8月份之前的版本,初始化后如果有不一致,就需要重点注意这个版本问题了,后续修改后会有些许代码不一样

    话不多说,进入本次文章正题。

    • vue-cli
      • build
        • build.js
        • check-versions.js
        • dev-client.js
        • dev-server.js
        • utils.js
        • vue-loader.conf.js
        • webpack.base.conf.js
        • webpack.dev.conf.js
        • webpack.prod.conf.js
      • config
        • dev.env.js
        • index.js
        • prod.env.js
      • src
      • static
      • .babelrc
      • package.json

    以上是vue-cli初始化项目后的纯净项目目录,大家应该都是这样,注意不是简化版脚手架 webpack-simply,这个就没啥好讲了= =。。。就一个webpack基本配置,没有热加载和 eslint 的。

    一、首先我们直接看 package.json 中的脚本命令,从执行处开始倒推vue-cli来讲解主题

    // package.json
    这里写图片描述

    这里只截取部分需要讲解的地方,大家可以看到初始化项目 install 的vue版本是@2.3.3,截取这个部分是为了让大家看到我是在说新的,不是旧的= =

    首先我们看到 npm run startnpm run dev 是一样的,都是跑 build/dev-server.js 所以归于一个来讲,所以这里我们其实只需要讲解2个命令
    1、npm run dev
    2、npm run build

    二、npm run dev

    1、首先大家打开后,进去看到第一行是 require('./check-versions')() 即加载当前目录下的 check-versions.js 并直接执行此函数,见名思意,其实这里的用处就是检测我们当前的 node 进程中运行的 node 与 npm 的版本,如果不符合会在 cli 下返回相应错误,为此我决定带大家看看里面有哪些包以及是如何做到的,请看下一点;

    2、这里我以图片的形式来代替代码块,之后我会在图片下插入注释及所有包的文档地址供大家参考,不然看起来就全是字了,我自己都看不下去
    // check-version.js 第一块解释

    check-version.js 第一块

    chalk 包 –正如上传者对包名的简介
    “Terminal string styling done right” 终端字符串样式修正,说白了就是在 cli 界面可以显示花里花俏颜色的字
    semver 包 –用于取版本后的版本对比
    后续有个api satisfies(version, range): Return true if the version satisfies the range
    可以字节取版本号的字符串与一个范围作对比, eg: semver.satisfies(1.2.3, >=2.0.0) 这里就返回 false 了,因为不满足
    shelljs 包 –可以直接以代码的形式在命令行跑shell,这个不多说了,文档里写的太清楚

    exec 函数,开辟一个子进程,执行node子进程 execSync 的方法,它会将传入的命令跑完后,在子进程被结束时返回其结果,这里返回后还转为为了字符串的形式并前后去空格, 转化为字符串时为了方便后续使用我前面说的 semver.satisfies(version, range)

    随后建立 versionRequirements 数组里面有2个对象,一个是用来验证 node 的版本一个是 npm semver.clean 获取过滤后的版本号,过滤只有 X.X.X 的形式 shell.which('npm') 获取npm在当前环境下的路径,存在则会返回路径地址,好,下面就可以解释check-versions.js 的下一段exports的函数了

    check-versions.js export的函数

    这里大家看到就很简单了,对 versionRequiremenets 数组进行循环,并去检测当前版本是否符合 package.json 内定义的版本,若不符合则黄色字体输出一些info,红色字体 cli 界面输出当前版本,绿色字体输出需要的版本,最后退出这段检测进程并返回 1 作为状态码。
    好,check-versions.js 分析结束,下面我们回到 dev-server.js,我们还是以图片加注释来讲解

    3、dev-server.js 代码及node包的分析

    dev-server.js 第一部分

    首先我们看到先将 config/index.js export的对象复制给了 config 变量(若不指定目录下哪一文件会默认加载该目录下的index文件), 而 config/index.js 的分析解读我们留到后面讲,这里用到什么就讲什么。

    紧接着赋值了当前进程的环境,加载的是 config.dev.env => require(‘./dev.env’).NODE_ENV => ‘development’, 这样写大家应该看的懂吧, 因为 dev.env.js export的对象其实就是{ NODE_ENV: ‘development’ }

    接下来引入各类包
    opn 包 –就是一个可以打开各类文件或网站的工具包
    用法非常简单,文档也简洁 eg: opn(‘http://XXX.XXXX.XX‘, {app: [‘google chrome’]}) 或者 opn(‘file.png’).then(), 支持promise结构
    path 包 –node自带的包,可用于路径的合并,不用再拼字符串啦!
    express –这个不多说了吧,node 的一个 web 框架,自行学习,通过express封装简化了node的操作 eg: http.Server().listen() => new express().listen(), 其他相关操作请各位自行看文档
    http-proxy-middleware 中间件 –用于请求的代理设置
    eg: proxy 中间件用法
    (可用于解决本地开发的跨域问题,在请求时服务器会像代理服务器请求后在本地返回,相当于一个中间层的处理)

    这里首先定义监听的端口,先像node进程中获取,查看是否服务端监听的端口,若无则读取 /build/index.js 中 dev 下的 port 的值,默认为8080

    最后赋值代理配置 proxyTable, 实例化 express , 调用 dev 配置进行webpack的编译

    紧接着进行的就是引入 webpack 的 dev-server 中间件了,这是 node 中引入的用法,webpack3之后我们使用 dev-server 在 webpack 中可以不用在 package.json 后加命令后缀了,在基本配置中加上即可, publicPath 指稍后需要保存到哪个路径

    devMiddlware 中 quiet 代表不要输出任何东西在控制台, 然后引入在引入 dev-server 的基础上引入 热重载中间件 webpack-hot-middlewarelogquiet 一样, heartbeat 为更新客户端(在这里我们就是网页了)的时间间隔,与在没有 socket 之前的聊天室同理,我说心跳连接大家应该都懂了,也就说这里就是 2s 会检测一次客户端是否有变化,若有变化后续我们会有一个回调来执行刷新,这里还需要重点说的是

    1、webpack-hot-middleware 依赖于 webpack-dev-middleware, 两者都是在 webpack 编译后进行绑定;
    2、使用 dev-server 后将不会产生文件流输出到磁盘中,而是存在当前服务器内存中,所以 npm run dev 你是看不到有文件生成的,我们在基本配置中的 publicPath 目录一定要填 ‘/’ 根目录下的XX,不能使用相对路径,因为它将保存在服务器下,本地相对路径无法读取,否则会出现 cli 在跑 dev-server 但是页面没更新也没办法重载的情况,因为输出流地址错误了;

    相关文档
    webpack-dev-middleware
    webpack-hot-middleware
    webpack3中的dev-server配置

    dev-server.js 第二部分

    我们继续开始第二部分的分析
    上来直接执行 plugin 的 compilation 参数,如果我没翻译错的话, compilation 参数,顾名思义这里是整个编译过程中最主要的生命周期,所有的模块都会被重载,所以在这里我们需要进行的当然就是热重载后的刷新页面或替换模块了。然后再执行我们事先安装的 webpack 插件 html-webpack-plugin 绑定到 compilation 中的事件 ‘html-webpack-plugin-after-emit’ 来扩展我们对 HTML 的操作,在回调内我们对热重载中间件执行其事件流后添加的 publish 方法向服务器进程发出重载通知,里面第一个参数是 一个对象,我们只需要改变后续执行的 action 为 ‘reload’ 就行,publish的参数文档中没提,但我们在源码中可以很容易的找到它其实就是个事件流的通知发布,而 action 为 ‘reload’ 这个是我们在 /build/dev-client.js 中自定义的事件行为,当等于 ‘reload’ 时,执行的是 window.location.reload(), 当然这里写明了客户端的配置中是添加了 reload = true 的,下面是 build 目录下的 dev-client.js

    dev-client.js

    下面是 webpack-hot-middleware/middleware.js 中的部分源码

    创建事件流并声明 publish 函数为一个闭包

    事件流的状态发布,其中包含了定义的一些参数

    接下来的遍历 proxyTable 的配置就简单了, 前面已经提到 proxyTable 是从 config/index.js 中的 dev 下调过来的,我们看到里面的配置其实 key 值就对应了 http-proxy-middleware 这个中间件的 app.user(context, options) context, value 就对应了 options , 然后通过遍历,依次添加代理。

    下面的几个 express 的 use 大家看注释应该都懂啦,
    1、使用 connect-history-api-fallback 这个包来处理单页应用中的历史记录,避免单页情况下,访问二级子组件时去 get 这个地址不存在而导致 404 ,具体大家自己去看文档吧~还可以正则匹配路由 redirect 回指定页面;
    2、执行 dev-server ,让 webpack 命令执行;
    3、执行热重载插件;

    下来继续,监听 staticPath => /static 访问它相当于访问 ./static 目录下的静态资源,express.static(root, [options]) 是 express 唯一内置的中间件,用于挂载访问静态资源,最后声明监听的 hostname + port, 然后创建一个 promise, 抽出 resolve 方法,第二段结束~

    接下来是 dev-server.js 的最后一部分了

    dev-server.js 第三部分

    调用 dev-server 中间件的 api waitUntilValid(callback) 用于 webpack-dev-middleware 绑定生效或再次生效后的回调,在里面我们看到的是只要 autoOpenBrowse 存在且 当前 node 进程的环境不是测试环境就利用 opn 包打开我们监听的 uri , 最后主函数都跑完后调用 promise 的 resolve, 这个函数实际上是自定义的,因为大家可以看到最后一段代码 exports 的对象

    最后 listen 开启服务监听 port 端口, 然后 export 一个后续可供操作的对象, ready 是 dev-server 的配置服务都完全启动且打开页面后的一个回调, close 方法则是关闭服务,抽出这个对象以防后续可能会在代码中手动操作,从这里开始由 dev-server 配置的本地服务就跑起来并且页面打开为大家看到的样子了,ok~ npm run dev 终于完结!!!

    三、npm run build

    讲这段之前大家一定要记得看 /build/webpack.base.conf.js 喔,这是webpack的基本配置,是一些加载器的配置以及入口及出口文件的配置,注意看出口文件的公共路径 publicPath 中的判断,你就知道 /config/index.js 里的 assetsPublicPath 是干啥的了。

    webpack的基本配置文件

    在里面还配置了对 vue-loader 的一些配置,例如是否进行 sourceMap 压缩代码, sourceMap 其实就是进行代码压缩以及文件合并,减少加载页面时的 http 请求, 而生成的 map 后缀文件里面记录了调试压缩后的代码后对应找到源文件的行数与列数位置,方便调试。
    阮老师的 SourceMap 详解

    好的,上正文, npm run build 跑的就是 node build/build.js , 我们和 dev-server.js 一样去分析 /build/build.js 即可。

    build.js 第一部分

    上面讲过的一些包和代码分析我就不再讲了,例如 ‘./check-versions’、 chalk 包这些
    ora 包 –这真的是个有趣的包,它用于在 cli 界面下显示五颜六色的 loading 动画,原理就是按照时间间隔遍历字符串哈哈,如果想要更多有趣的loading动画,去看看这个包吧 cli-spinners 看看它的 spinners.json 我惊了(@ο@)
    rimraf 包 –让你可以直接用linux的 rm -rf 命令
    /build/webpack.prod.conf 这里的合并了 /build/webpack.base.conf.js 内的配置,简单来说就是对象的合并 , 在 /build/dev-server.js 内也有, 我放到后面统一单独讲

    后面就是启动 ora 这个包啦,这个非常简单,看文档吧,例子我都不举啦,直接下一段了。

    build.js 第二部分

    执行 rm 命令删除指定相对路径下 build 出来的目录下的所有文件,成功后的回调内,spinner.stop() 停止 loading 动画的循环,执行执行 webpack 的 node API 开始打包并向当前 node 的 webpack 打包进程输入流 写入 stats 的属性配置,相应配置的含义我会在下面贴出来文档,大家自行查询。
    Node API process.stdout.write()
    webpack stats配置列表
    最后输出一些说明日志,build.js 就是这么简单,完结~接下来就是一些 webpack 的配置说明了~

    四、webpck.base.conf.js 内的一些配置说明

    首先先第一段吧

    webpack.base.conf.js 第一部分

    utils.js 与 vue-loader.conf.js 单独讲不好讲,我们再下面代码中穿插着讲, 因为 utils.js 中输出了多个函数来方便调用。
    entry: webpack 打包的入口文件,type: String || Array || Object
    output: webpack 出口配置,打包后的目录及模块加载的公共路径及文件名等配置,具体查看webpack文档即可,里面有很详细的讲解,这里不做细讲
    resolve: webpack 的模块解析器,extensions 可解析的扩展名, 值要为数组,里面的扩展名不用说了吧? alias 别名, 即解析到对应的 key 字符串时会自动对应解析为 key 对应的 value 值,用过 linux 别名的应该都懂~下一段

    webpack.base.conf.js 第二部分

    模块加载器规则,这里我说几个重点及我还记得的遇到过的坑
    1、webpack2后 loader 加载器的值不能简写了,如 babel-loader, 我们以前会不写 loader 而直接写babel,会自动补全为 ‘babel-loader’,但是现在不会了,3更要避免这种写法;

    2、webpack1中我们使用Eslint时会时规则会写在 preLoaders 里, 注意现在不可以了,而是由enforce: 'pre' 代替为加载器预处理;

    3、当你想在 react 项目中, 在需要用到组件内的状态管理 this.setState 时,我们会选择利用 class XXX extends React.Component 的方式新建类来继承为 React 组件类,为了能在里面直接使用箭头函数而不用麻烦的使用Fucntion.prototype.bind(context, [args]), 我们会安装2个 babel 插件 babel-preset-stage-0babel-plugin-transform-class-properties, 装完后注意要在 js 文件的加载器那将原本写为 exclude: '/node_modules/' 改为 include: '你自己需要使用babel转换的目录', 否则会报错 $export 不是一个函数,就像这里 vue-cli 的 eslint 需要处理的目录一样, 我记得没错的话好像是 babel 插件的锅,这里我还没有深究其原因= =。。。如果大家知道,希望能留言告诉我,蟹蟹;

    4、我们来讲讲这里的 vueLoaderConfig 即 vue-loader.conf.js 的配置是干啥的。
    在解释 vue-loader.conf.js 之前我们必须先看 build/utils.js 里的 cssLoaders 这个方法里面干了什么

    utils.js export出来的 cssLoaders 方法

    首先在 utils.js 中我们看到引入了 extract-text-webpack-plugin 这个 webpack 插件, 使用它的 extract api 可以创建额外的加载器在已存在的加载器下,所以这里用于 cssLoaders 就是为了使 .vue 文件的加载器可以使用多个 css 预处理器的加载器, 而在 /build/vue-loader.conf.js 中我们看到 loaders 下的 extract 开启的条件是当前 node 进程的环境是生产环境时便满足 cssLoaders 使用 ExtractTextPlugin extract,因为生产环境打包需要释出 css 文件,所以最终返回的对象也是为此处的 loaders push 各类 css 预处理器的 loader,所以整体上我们大概已经知道这段函数其实就是在生成 ExtractTextPlugin对象或loader字符串,在非生产环境的条件下,我们不需要 ExtractTextPlugin 来生成真正的 css 文件,所以存在 extract 这个判断,所以在 webpack.base.conf.js 中的 vueLoaderConfig 在非生产环境下就是一个 loaders 对象用于 .vue 文件中可以处理各类 css 预处理器, 在 generateLoaders 内就是对每种 css 预处理器需要的 loader(Array) 进行了一个拼接。

    extract-text-webpack-plugin extra 说明

    5、 vueLoaderConfig 说完了,我们回过来继续分析 utils.assetsPath 这个用于生成静态资源路径的函数,这个函数就非常简单了,就是简单讲生成路径组装起来,传入的参数就是生成对应的文件目录下与名字拼接起来的字符串,这里需要注意的是 .ext 代表一个通用的文件扩展名,当我们不确定文件为什么扩展名时使用,在这里 [ext] 就是说会自动去匹配扩展名。

    ok,base的配置讲完啦

    五、webpack.dev.conf.js 内的一些配置说明

    由前面分析我们已经知道, webpack.dev.conf.js 是在开启 dev-server.js 时引入的 webpack 开发环境的配置,现在我们就来看看开发环境的配置到底与 base 的基本配置有什么不同。

    webapck.dev.conf.js

    照常,先解释一些引入包的作用

    html-webpack-plugin —前面已经大概给大家了解过了,这是一个与webpack绑定起来作为单页首页的唯一 html 首页模板,在其中我们可以声明各类渲染模板作为首页也是可以的,如 ejs 、jade 都是可行的,一般作为 vue 或 react 的根节点的绑定,建议利用渲染模板来替代首页,可以更加灵活的加入各类参数的配置,如日常开发环境下的测试账号信息等配置,具体用法大家自行参照文档,相对简单。

    friendly-errors-webpack-plugin —可有可无的插件,主要为了开发体验,是用于自定义 cli 界面报错的友好型信息,开发者可以自定义信息,或使用插件默认的,主要用于快速定位报错位置。

    首先遍历基本配置中的入口文件,令其在 dev-client.js 所自定义的热重载事件流下作为入口给 webpack 打包,令后续 webpack 的 compiler 事件流中有 reload 这个动作,这就解释了前者 dev-server.js 中的webpack 为何会拥有 reload 这个 action了

    其次则是利用 webpack 的 merge 合并 base 中的配置, 这个 merge 类似于 jq 的 $.extend 或 underscore 中的 _.extend , 用于合并对象,接下来让我们看看在 base 配置的基础上还加了什么;

    1、在 rules 中增加了多个 css 预处理器的加载器

    utils.js 中的 styleLoaders 方法

    我们看到 styleLoaders 方法最后返回的实质上就是 cssLoaders 中返回的各类 css 预处理器的 loader, 前面我们在 base 中使用 cssLoaders 作为 .vue 这个模板文件 loader 下的子加载器,而这次我们则利用 cssLoaders 作为 webpack 的外层配置,即不在依赖于 .vue 文件了,使其在任何情况下都可以直接使用,因为在此处合并了各类 css 预处理器的加载器规则。

    2、利用 devtool 添加 sourceMap 配置

    #cheap-module-eval-source-map 这种生成 sourceMap 的命令被描述为: 不包含列信息,同时 loader 的 sourcemap 也被简化为只包含对应行的。最终的 sourcemap 只有一份,它是 webpack 对 loader 生成的 sourcemap 进行简化,然后再次生成的。(其实这么多种 sourceMap 的方式,我也并不理解。。。),这里姑且就当做是开启了 sourceMap 就好~

    3、各类新添加的 webpack plugins
    definePlugin: 打包时会将你在其中自定义的 key 作为全局变量,而 value 就是它的值了, 在这里我们看到是定义了 process.env 这个全局变量为 config.dev.env 即 config/dev.env.js export的对象,值得注意的是里面有个键名为 NODE_ENV 为 ‘development’, 这就让我们后面每次用到这些配置的地方不用去手动引入更改了, 而一直是注入在项目中的,减少了人为手动操作而带来的忘记更改的错误,详细用法大家可以去官网看,或看这篇博客 webpack.DefinePlugin({}) 的用法
    HotModuleReplacementPlugin: 不多说了,热替换的插件,使你可以在不用重绘整个页面的情况只是单独重新加载部分模块,这样改动小时就不会老是要刷新整个页面了。
    NoEmitOnErrorsPlugin: 可以跳过存在的错误而继续webpack的编译,即使部分模块报错也没事。
    HtmlWebpackPlugin: 上面已经提过了,这里就是添加了相关配置而已,令 index.html 成为模板且文件名也为它, inject 指注入所有相关配置到 html 中的 body 中, true 为默认 body,可定义为 ‘head’ 注入到 head 中。
    FriendlyErrorsPlugin: 前面已经提过了。

    六、webpack.prod.conf.js 内的一些配置说明

    老套路,先说包,再说配置

    webpack.prod.conf.js 第一部分

    copy-webpack-plugin —webpack用于复制文件以及文件夹的插件到指定目录下;
    extract-text-webpack-plugin —用于分离 css 文件,前面已经分析过;
    optimize-css-assets-webpack-plugin —用于优化 css 的代码及压缩,开发环境下没必要。

    1、新增的 rules 与 detool 在 webpack.dev.conf.js 中已经解释过,这里只是参数变为了生产环境的 build 配置而已, 出口文件的配置也是根据 assetPath 函数判断为 build 下的配置而已,变更的只是生成的 js 文件独立放到了一个 js 的文件夹下而已,这些没什么讲的了;

    2、新增的 plugins 需要讲讲, 与 dev 环境下一样的就不讲了
    UglifyJsPlugin: 用于压缩 js 文件 [UglifyjsWebpackPlugin];(https://webpack.js.org/plugins/uglifyjs-webpack-plugin/#components/sidebar/sidebar.jsx)
    ExtractTextPlugin: 里面的 filename 配置值压缩出的 css 文件的路径;
    OptimizeCSSPlugin: 里面 cssProcessorOptions 内的 {safe: true}, 记得加上,这是保证 build 后由于插件为你 css 代码优化过程中令某些 css 样式不被重置,说白了就是优化过程中,不让他重置代码,比如 z-index 这种层级关系被优化就蛋疼了;
    HtmlWebpackPlugin: 上面说过好多次了,这里提一下新增的几个配置的意思↓

    • removeComments: 删除 HTML 中缩写内容的空格以及换行,比如一般来说我们,最小化配置文档在这 [html-webpack-plugin minify配置列表](https://github.com/kangax/html-minifier#options-quick-reference)

    • collapseWhitespace: 去掉标签内的无用空格,并会合并相邻的相同标签

    • removeAttributeQuotes: 删掉标签内一些无用及未被使用属性,如 id=” class=” 会被直接删掉,而本来在html attribute 中 id class 这类就是被解释为字符串,所以也会删掉 id=”id” 这种情况下的双引号

    CommonsChunkPlugin: 将 js 分割为独立的文件;
    CopyWebpackPlugin: 这里复制了挂载在 static 文件夹下的静态资源,确保 static 内的图片或其他文件不被压缩打包。

    3、最后的 2 个判断而加载的 2 个插件
    compression-webpack-plugin: 在 build 配置中开启 gzip 的情况下, 引入 webpack 压缩插件 assest: 被压缩的目标; algorithm: 压缩算法; test: 符合该正则的配置才被压缩,默认是所有的; threshold: 只压缩被这个配置值更大的文件; minRatio: 只有比这个压缩比例压缩出来更优的文件才进行压缩,默认是0.8,即压缩到80%
    webpack-bundle-analyzer: 在开启模块绑定分析报告的情况下,引入这个插件,好像是会生成一个可视化界面供你查看 webpack 绑定模块分布情况(没用过这个= =) webpack-bundle-analyzer

    七、总结

    写着写着就写了这么多了,整个论文一样。。。 阅读体验应该会比较差,我已经尽自己可能写的够详细了,其中若存在比较含糊与错误的地方,欢迎大家指出,我会及时纠正~蟹蟹,我也不想这么多的,真的好累啊~~(>_<)~~。。。怎么就这么多了~

    展开全文
  • 之前仿造uploadify写了一个HTML5版的文件上传插件,没看过的朋友可以点此先看一下~得到了不少朋友的好评,我自己也用在了项目,不论是用户头像上传,还是各种媒体文件的上传,以及各种个性的业务需求,都能得到...
  • 最近公司项目,有对文件的导出(下载)的功能,当然普通的下载方式,比如访问一个后台文件流地址,直接生成对应的文件,下载即可,地址栏也可携带一些控制参数等(例如?a=1&amp;b=2),但这个项目的api接口...
  • 1、Doctype作用?标准模式与兼容模式各有什么区别? (1)、<!DOCTYPE>声明位于HTML文档的第一行,处于 <... 标签之前。告知浏览器的解析器用什么文档标准解析这...在兼容模式,页面以宽松的向后兼容的方...
  • shell扩展

    2018-04-11 11:47:31
    第七城市编程开发平面设计数据库设计赏析关注第七城市Email:service@7-inc.com编程PHPJavaPython.NETRubyIOS安卓设计3DS MAXIllustratorPhotoshopCoreldrawCAD赏析插画平面网页建筑摄影三维UIWordExcelPPTWinLinuxOS...
1
收藏数 8
精华内容 3
关键字:

dva react中截取字符串