Phaoer


  • 首页

  • 标签

  • 分类

webpack配置中的contentBase和publicPath

发表于 2019-10-10 | 分类于 前端 , node , 构建工具

前言:
这里主要介绍output中的publicPath和devServer中的contentBase和publicPath
首先明白两点,

  • 在webpack.config.js文件中,output配置只在production环境下起效,devServer只在development环境下有效。
  • devServer运行下所编译的文件皆存在于内存中,不会改变本地文件。在服务运行中如果内存中找不到想要的文件时,devServer 会根据文件的路径尝试去本地磁盘上找,如果这样还找不到才会 404

output.publicPath

我们最常接触的output的配置是:

1
2
3
4
output: {
path: __dirname + "/build",
filename: "[name].bundle.js"
}

那么这里publicPath是用来干嘛的?
其实publicPath被webpack的插件(url-loader,html-webpack-plugin)用于在production环境下更新引用的静态资源的url。

栗子:

1
2
3
4
output: {
.........
publicPath:'https://a.cdn.com/'
}

那么当你执行打包过后你的静态资源路径就会被加上publicPath的路径

development:

1
2
3
.div{
background:url(./1.png) center top no-repeat;
}

production:

1
2
3
.div{
background:url(https://a.cdn.com/1.png) center top no-repeat;
}

devServer.contentBase

dev-server

引用官网的话就是:
告诉本地服务从哪里提供内容且只有在您想要提供静态文件时才需要这样做

其实就是index.html所在的目录

devServer.publicPath

当启动devServer的时候,文件也会被编译,上面说到它只存在于内存中。
publicPath其实就是指定外部访问编译文件的路径

栗子:

devServer配置

1
2
3
4
devServer: {
publicPath:'/build/',
inline: true
}

index页面

1
<script src="build/main.bundle.js"></script>

那么当我们的devServer启动的时候做了哪些事儿:

  • 首先加载contentPath下面的index.html(由于没有设置),则访问的便是

    1
    http://localhost:8080/
  • 加载index.html里的静态资源,由于设置了publicPath,那么编译后的文件提供给外部的访问路径就是contentPath + publicPath,也就是

    1
    http://localhost:8080/build/
  • 由于在内存中找到了该文件则不去读取硬盘中的文件

注意项

需要注意的一点是:
当你在项目中用到了html-webpack-plugin的时候,请保证output和devServer的publicPath路径一致
因为html-webpack-plugin在嵌入静态资源的时候使用的是output的publicPath,会导致在devServer运行的时候加载资源报错

设计模式之ReFlux

发表于 2019-09-28 | 分类于 前端 , javascript , react

Reflux Version 6.4.1

  • 单向数据流思想
  • Flux进阶版

相比于Flux,在Reflux中除去了Dispatcher,即在Reflux中每一个Action就是一个Publisher,每一个Store就是一个Listener,因此我们可以在store中指定监听某一个Action,一旦Action触发则调用绑定的方法来修改数据

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"webpack": "^3.6.0"
},
"dependencies": {
"react": "^15.6.2",
"react-dom": "^15.6.2",
"react-router": "^2.4.1",
"reflux": "^6.4.1"
}

使用方法

引入

1
2
3
4
5
6
7
8
9
import React, { Component } from 'react';

import ReactDOM from "react-dom";

import Reflux from "reflux";

import Actions from '../app/action/actions'; //这里我是将Action和Store分开了,实际开发中建议这样,方便管理。

import Stores from '../app/store/stores';

创建Actions

1
let Actions = Reflux.createActions(['action1','action2']);

创建Stores

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Stores extends Reflux.Store{
constructor(){
super(); //切记先调用super方法,Es6中子类没有this
this.state = { //初始值,和react一样
name: 'Irwin Pu',
sex:'male'
};
this.listenables = Actions; //监听action,每个action都会自动注册一个on + actionname的方法
}

onAction1(){
console.log('name:' + this.state.name);
}

onAction2(){
console.log('sex:' + this.state.sex);
}

}

挂载Stores

这里我是最喜欢的,reflux给我们提供了一个Reflux.Component类,而且继承了React.Component,唯一区别就是Reflux.Component会将Store中的state自动添加到当前组件的state中,nice啊~
需要注意一点的就是在调用componentWillMount和componentWillUnmount时候。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyComponent extends Reflux.Component{ 
constructor(){
super();
this.state = {}; //store会将它的state加到组件的state里面去
this.store = Stores; //assign Stores (可用数组assign多个)
// this.storeKeys = ['name']; // 设置后只有name会被添加到state当中
}

componentWillMount()
{
console.log('Hello Reflux');
super.componentWillMount(); //官方文档提到直接调用会覆盖reflux.Component的方法,这里调用父类react的componentWillMount
}

render(){
var name = this.state.name;
return <div>Welcome {name} <input type='button' onClick={ () => {Actions.action1()} } value='click' /></div>
}
}

redux

发表于 2019-06-20 | 分类于 前端 , javascript , react

前言

你可能不需要Redux,那就别瞎搞!!!
Redux 的适用场景:多交互、多数据源。

简介

三个点:action,view,state。

在Redux中 一个 State 对应一个 View。只要 State 相同,View 就相同。而且State 是View导致的。

简单的来说就是用户接触不到State ,只能在View的层面,操作View通过Action发出通知,从而改变State。

当Store 收到 Action 以后,会返回一个新的 State,来改变View。这个过程就叫做 Reducer,且它是一个函数,是一个纯函数

重要:Action只是通知我要改变了,而Reducer才是告诉你怎么去改变,告诉你怎么做才符合社会主义核心价值观,告诉你怎么才无愧于一个当代青年人。

Redux中的state是Redux自己的数据,React通过store.getState来获取,store.dispatch来修改

combineReducers

由于整个应用只有一个 State 对象,包含所有数据,对于大型应用来说,这个 State 必然十分庞大,导致 Reducer 函数也十分庞大

  • 拆分reducer
    将不同的action交给不同的函数来计算,最后合成一个reducer
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    import { combineReducers } from 'redux';

    let defaultState = {
    count:0,
    otherdata:0
    }

    const count = (count = defaultState.count,action) => {
    switch (action.type) {
    case 'increase':
    return count + 1 ;
    default:
    return count
    }
    }

    const otherdata = (otherdata = defaultState.otherdata,action) => {
    switch (action.type) {
    case 'reduce':
    return otherdata - 1 ;
    default:
    return otherdata
    }
    }

    export default combineReducers({
    count,
    otherdata
    });

MiddleWare

实际项目中涉及到很多异步操作,那么在Redux要实现异步,得依靠中间键,中间键如果是非必须的话尽量使用现成的中间键。自己造轮子。。。。。。。。。。你懂得~

一般我们要想添加其他功能选择是在发送Action的时候添加,那就是我们的store.dispatch方法上做文章。
但是store.dispatch作为Action的载体只能接受一个对象,我们添加不了其他功能。

这里就需要用到redux-thunk中间键。

使用方法:命令行在目录下执行:

1
cnpm install --save redux-thunk
1
import thunk from 'redux-thunk';

使用过后store.dispatch这个方法可以接受一个函数作为参数,ok,可以搞事了!
废话不多说
实践才是检验真理的唯一标准。

这里只是很简略的介绍了Redux,基本的使用方法请参照Redux中文网

React-Redux

为什么要使用React-Redux

  • redux为我们解决了什么?
    • 使用了redux我们在数据传递从下到上的传递不需要一层一层的上报,redux就相当于一个战略指挥中心,你只需要告诉redux,然后让redux去重新计划(渲染)
  • redux渲染
    • 首先我们得调用store.getState()方法获取到改变后的state,然后再赋值到this.props属性上,然而这只得到了数据。我们知道react的重新渲染需要调用setState方法,而redux则提供了store.subscribe(render)方法,一旦state改变即调用render方法重新渲染
  • redux没有解决的问题
    • 使用了redux解决了向上传递的问题,但是向下的传递依然是一层一层的,无法直接通知到某个子组件,解决的方案也有,就是:
    • 在父组件利用store.getState()获取值
    • 在通过原生react的方式一层层往下传递

而react-redux恰好解决了这个问题。

React-Redux解决数据下发的问题

  • 组件通过在connect时的mapStateToProps函数中映射state值即可直接通过this.props.xxx获取到值
  • 组件通过在connect时的mapStateToProps函数中映射相同的state,即可实现不同组件数据共享

在React-Redux中所有组件分为UI组件(presentational component)和容器组件(container component)两大类
UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑

另外React-Redux 提供connect方法,用于从 UI 组件生成容器组件

connect方法有两个参数mapStateToProps和mapDispatchToProps

mapStateToProps:映射Redux state到组件的属性

  • mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲

mapDispatchToProps:映射Redux actions到组件的属性

  • mapDispatchToProps用来建立 UI 组件的参数到store.dispatch方法的映射
  • 它可以是一个函数,也可以是一个对象。如果mapDispatchToProps是一个函数,会得到dispatch和ownProps(容器组件的props对象)
  • 定义了 UI 组件的参数怎样发出 Action

需要获取store值的组件或者发送action的组件都需要connect

当容器组件生成过后React-Redux提供了组件,让容器组件拿到state。然后在将根组件包起来,这样所有的根组件就能够拿到state了。
像这样:

1
2
3
4
5
6
ReactDOM.render(  
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

注意:这里App并不能直接获得store中的数据,必须通过connect生成ui组件,才能通过this.props.xxx获取。
那么子组件之间的共享就必须在两个组件connect时的mapStateToProps函数中映射同样的state

简单说一下bindActionCreators

bindActionCreators是redux官方提供的自动合并action并发送dispatch的方法

code:

1
2
3
4
5
6
7
import * as actions from './actions.js'; 

function mapDispatchToProps(dispatch){
return {
actions:bindActionCreators(actions,dispatch)
}
}

这样的做法便捷,但是带来的问题是action合并过后,某个组件要发送action时,那么actions就必须要一层一层的往下传,所以不建议使用

Do it

使用webpack来构建

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 {
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack --mode production",
"dev": "webpack --watch --mode development"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"css-loader": "^1.0.0",
"style-loader": "^0.21.0",
"url-loader": "^1.0.1",
"webpack": "^4.15.1",
"webpack-cli": "^3.0.8"
},
"dependencies": {
"react": "^16.8.3",
"react-dom": "^16.8.3",
"react-redux": "^6.0.1",
"redux": "^4.0.1",
"redux-thunk": "^2.3.0"
}
}

项目目录:

这里建议将action和reducer分离开来,因为项目较大的话,你可能会有很多种不同的action或者reducer,方便后期维护。

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import React, { Component } from 'react';  

import ReactDOM from 'react-dom';

import { createStore , applyMiddleware} from 'redux';

import { Provider, connect } from 'react-redux';

import thunk from 'redux-thunk';

import * as actions from './actions.js';

import reducer from './reducer.js';

import Top from './common/Top.js';

import Content from './common/Content.js'

require('./main.css');

class App extends Component{
render() {
const {onGetDataClick,list} = this.props;
let name = "XXX系统";
return (
<div>
<Top name={name} />
<button onClick={onGetDataClick}>Get</button>
<Content data={list} />
</div>
);
}
}

let store = createStore(reducer,applyMiddleware(thunk)); //使用中间键

function mapStateToProps(state) { //将Redux state映射到组件的属性
return {
list : state.list
}
}

function mapDispatchToProps(dispatch){ //定义事件的action
return{
onGetDataClick:()=>dispatch(actions.getDataAction),
}
}

App = connect(mapStateToProps, mapDispatchToProps)(App) //绑定到App组件

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

actions.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const getDataAction = (dispatch) => {  
$.ajax({
url:"xxxxxxxxxxxxxxxxxxxxxxxx",
type:"post",
dataType:"json",
data:{},
success:function(res){
dispatch({type:"RES_SUCCESS",data:data})
},
error:function(){
dispatch({type:"RES_ERROR"})
},
compelet:function(){
dispatch({type:"RES_COMPELET"})
}
})
}
export {getDataAction}

这里我们dispatch第一次执行咱们的请求,根据不同状态再发送Action来触发 Reducer

reducer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const initialState = {  
list:[]
}

export default (state = initialState, action) => {
switch (action.type) {
case 'RES_ERROR':
return {
list:"Error"
}
case 'RES_COMPELET':
return {
list:"Compelet"
}
case 'RES_SUCCESS':
return {
list:action.data
}
default:
return initialState;
}
}

Reduce中我们接收到一个Action,然后返回一个State,然后映射到组件的属性上,实现View的更新

Content.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React, {Component} from 'react';

export default class extends Component{
render() {
return (
<div className="content">
<table id="managelist">
<thead>
<tr> //此处不可省略tr,不然react会抛出警告
<th>XXX</th>
<th>XXX</th>
<th>XXX</th>
</tr>
</thead>
<tbody>
{this.props.data.map(function(item,index){
return (
...............................
)})}
</tbody>
</table>
</div>
);
}
}

React 之性能优化

发表于 2019-06-20 | 分类于 前端 , javascript , react

包含父子组件的Tab切换

当我们将tab切换分离到子组件时很多人喜欢这样写

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class App extends Component{
constructor(props){
super(props);
this.state = {
};
this.type = 0;
}

tabSwitch(){
this.setState({
//change state
})
}

render(){
return (
<Tab renderFun={this.tabSwitch} type={this.type} />
)
}
}

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

子组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
export default class Tab extends Component{
constructor(props){
super(props);
this.state = {
tab:['tab1','tab2'],
current:this.props.type
};
}

tabSwitch(index){
this.setState({
current:index
},() => {
this.props.renderFun(this.state.current);
})
}

render(){
var _this = this;
return (
<div className='tab'>
{ this.state.tab.map(function(ele,index){
return <div key={index} className={'tab_' + index + ' ' + (_this.state.current == index ? 'tab_active':'')} onClick={_this.tabSwitch.bind(_this,index)}>{ele}</div>
})
}
</div>
)
}
}

这样的写法实现功能是没问题。当我们切换tab时,将点击的tab栏目传递给父组件,父组件改变state去重新渲染tab列表。但是这里虽然功能实现,但是写法上说不太react。

我们在子组件点击的时候,setState改变了current,子组件重新渲染,然后回调点击的tab给父组件,父组件执行setState会开始更新过程,这时候父组件的shouldComponentUpdate返回的肯定是true,
此时按照componentWillUpdate-》render-》componentDidUpdate,父组件完成了更新过程,由于父组件的渲染,所以子组件也开始更新过程,
此时由于newProps == this.props,所以子组件的shouldComponentUpdate返回false,更新过程终止。

比较正规的写法:

React建议将关系到渲染的数据保存在state中。这里能勾关系到渲染的就是这个type了。因为type的改变会导致我们的列表改变

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class App extends Component{
constructor(props){
super(props);
this.state = {
type:0
};
}

tabSwitch(index){
this.setState({
type:index
})
}

render(){
return (
<Tab renderFun={this.tabSwitch} type={this.state.type} />
)
}
}

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

子组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
export default class Tab extends Component{
constructor(props){
super(props);
this.state = {
tab:['tab1','tab2'],
};
this.current = this.props.type;
}

tabSwitch(index){
this.current = index;
this.props.renderFun(index);
}

render(){
var _this = this;
return (
<div className='tab'>
{ this.state.tab.map(function(ele,index){
return <div key={index} className={'tab_' + index + ' ' + (_this.current == index ? 'tab_active':'')} onClick={_this.tabSwitch.bind(_this,index)}>{ele}</div>
})
}
</div>
)
}
}

微信小程序 获取用户信息登录(PHP)详解

发表于 2019-05-23 | 分类于 前端 , 微信小程序

一 前端

微信小程序以数据驱动的理念以及类jsx语法的形式,以高集成高度封装的方式开辟了H5新理念。

navigation传参

和一般的链接带参数一样’?data=xxx&list=xxx’,参数可以在onLoad函数的回调里获得

1
2
3
onLoad:function(option){
//console.log(option.data);
}

获取属性值

由于在小程序以数据驱动作为基本理念。我们要尽量避免去操作Dom。
组件的属性以data-param = “xxx”的形式写入,当该组件绑定了事件处理函数bindtap(不会阻止事件冒泡)或catchtap时,如bindtap = “tapName”,可以同过该函数获取该组件的一些信息,如下:

1
2
3
tapName:function(event){
console.log(event);
}

获取到的返回值如下(部分省略):

1
2
3
4
5
6
7
8
9
10
11
12
13
    "target": {
"id": "cid",
"dataset": {
"param":"xxx"
}
},
"currentTarget": {
"id": "cid",
"dataset": {
"param":"xxx"
}
}
........

这里需要注意的事,当获取某个组件的属性的值的时候,请使用event.currentTarget.dataset.param。当该事件没有阻止事件冒泡的时候,target和currentTarget(相当于this)的值是不一样的。

动画

小程序在使用动画的时候:

  • 组件上写入animation属性
1
<View animation="{{move}}"></View>
  • js中创建animation实例
1
2
3
4
5
6
7
8
9
10
11
page({
data:{
move:''
},createAnimation : function(){
this.animation = wx.createAnimation({
duration: 1000,
timingFunction: 'linear',
});
}

})
  • export导出动画并传递给animation属性
1
2
3
4
5
6
7
8
creatAction:function(){
this.createAnimation();
this.animation.top('50rpx').left('50rpx').step();
//step() 来表示一组动画完成
this.setData({
move : this.animation.export();
})
}

条件渲染

js中经常遇到显示隐藏等功能,这里有两种方法

  • wx:if条件渲染
    1
    <View wx:if="{{isShow}}"></View>

当isShow为ture是该View会被渲染
条件还可以if else配合使用

1
2
3
<View wx:if="{{num>1}}"></View>
<View wx:elif="{{num<1}}"></View>
<View wx:else></View>
  • hidden属性
    用法相似

如何抉择:
如果需要频繁切换的情景下,用 hidden ,反之则 用wx:if 。

二 后端(PHP)

session问题

小程序没有cookie,所以session机制也就不适用,当遇到接口需要检测登录状态时就没办法了。
解决方法:
我们可以将sessionid下发至前端,每次请求再在head中设置好sessionid,即可解决

wx.getUserInfo接口改动

不再主动呼出授权框,需要前端自己引导用户授权,按钮:

1
<button class='getinfo' open-type="getUserInfo" bindgetuserinfo="goauth_info" binderror="goauth_error">确认授权</button>

获取unionid

官方文档上说的还是比较清楚的

  • 1.调用接口wx.getUserInfo,从解密数据中获取UnionID。注意本接口需要用户授权,请开发者妥善处理用户拒绝授权后的情况。
  • 2.如果开发者帐号下存在同主体的公众号,并且该用户已经关注了该公众号。开发者可以直接通过wx.login获取到该用户UnionID,无须用户再次授权。
  • 3.如果开发者帐号下存在同主体的公众号或移动应用,并且该用户已经授权登录过该公众号或移动应用。开发者也可以直接通过wx.login获取到该用户UnionID,无须用户再次授权。

也就是说如果是2,3种情况,我们调用接口

1
https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code='.$code.'&grant_type=authorization_code

可以直接拿到unionid

1
2
3
4
5
{
"openid": "OPENID",
"session_key": "SESSIONKEY",
"unionid": "UNIONID"
}

如果是第1种情况
这我我们需要采用算法将wx.getUserInfo接口获取到的私密信息encryptedData,iv和session_key传递给后端进行解密获得unionid
PHP写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
include_once "wxBizDataCrypt.php";
$appid = 'APPID';
$sessionKey = $_SESSION["sk"];
$encryptedData = $_POST["encryptedData"];
$iv = $_POST["iv"];

$pc = new WXBizDataCrypt($appid, $sessionKey);
$errCode = $pc->decryptData($encryptedData, $iv, $data );

if($errCode == 0)
{
echo $data;
}else{
echo $errCode;
}

数据验证

通常我们在使用unionid在和自己的业务关联时,都会考虑有效性。但公众号的token有效性验证接口无法用于小程序
不过我们可以通过wx.getUserInfo接口获取到的参数rawData和signature传给后端,将rawData和session_key进行哈希加密,与signature进行比对来确保数据的有效性

1
2
3
4
if(hash('sha1',$rawData.$session_key) === $signature)
{
echo '验证通过';
}

React之Refs

发表于 2019-03-20 | 分类于 前端 , javascript , react

Refs是什么

引用官方的话:
Refs 提供了一种访问在 render 方法中创建的 DOM 节点或 React 元素的方式。

为什么需要Refs

在常规的 React 数据流中,props 是父组件与子组件交互的唯一方式。要修改子元素,你需要用新的 props 去重新渲染子元素。然而,在少数情况下,你需要在常规数据流外强制修改子元素。被修改的子元素可以是 React 组件实例,或者是一个 DOM 元素。在这种情况下,React 提供了解决办法。

用例

第一种用法

String 类型的 Refs

这种使用方法是老版本的用法,当然在16.4.1更新后,明确说了该种方法的Refs存在问题,所以为了不给自己埋坑,建议不要使用这种方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class App extends Component{
constructor(props){
super(props);
this.state = {
value:""
};
}

componentDidMount() {
this.refs.myinput.focus();
}

render(){
var str = {
"width":220,
"textAlign":"center",
"margin":"auto"
}
return (
<div style={str}>
<input type="text" ref="myinput" />
</div>
)
}
}

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

这个例子,当在第一次渲染后,input将会获得焦点。

第二种用法

下面我们改写下例子,来介绍第二种用法:创建 Refs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Child extends Component {
constructor(props) {
super(props);
this.state = {
text: '我是初始值'
};
}
subHandleClick(){
this.setState({text: '我被父组件调用了,改变了内容!'})
}
render(){
return(
<div>
{this.state.text}
</div>
)
}
}

class Parent extends Component {
constructor(props) {
super(props);
this.test = React.createRef();
}

handleClick(){
console.log(this.test.current);
this.test.current.subHandleClick();
}

render(){
return(
<div>
<input
type="button"
value="父组件调用子组件方法"
onClick={this.handleClick.bind(this)}
/>
<Child ref={this.test} />
</div>
)
}
}

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

这里说明一下refs值得类型:
首先ref的值取决于节点的类型:

  • 当 ref 属性被用于一个普通的 HTML 元素时,React.createRef() 将接收底层 DOM 元素作为它的 current 属性以创建 ref
  • 当 ref 属性被用于一个自定义类组件时,ref 对象将接收该组件已挂载的实例作为它的 current 。
  • 你不能在函数式组件上使用 ref 属性,因为它们没有实例。

第三种用法

第三种:回调函数用法

  • React 也支持另一种设置 ref 的方式,称为“回调 ref”,更加细致地控制何时 ref 被设置和解除。
  • 不同于传递 createRef() 创建的 ref 属性,你会传递一个函数。这个函数接受 React 组件的实例或 HTML DOM 元素作为参数,以存储它们并使它们能被其他地方访问。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class CustomTextInput extends React.Component {
constructor(props) {
super(props);

this.textInput = null;

this.setTextInputRef = element => {
this.textInput = element;
};

this.focusTextInput = () => {
// 直接使用原生 API 使 text 输入框获得焦点
if (this.textInput) this.textInput.focus();
};
}

componentDidMount() {
// 渲染后文本框自动获得焦点
this.focusTextInput();
}

render() {
// 使用 `ref` 的回调将 text 输入框的 DOM 节点存储到 React
// 实例上(比如 this.textInput)
return (
<div>
<input
type="text"
ref={this.setTextInputRef}
/>
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
ReactDOM.render(
<CustomTextInput/>,
document.getElementById('root')
)

Ajax 跨域请求

发表于 2019-03-11 | 分类于 前端 , javascript , jquery

如今大都要用到跨域的方法请求数据,网上的方法也是各式各样,这里我重点介绍两种不同的跨域形式。

跨域请求接口

主要介绍两种jquery的方式

$.getJSON

此方法简单粗暴,如果你对ajax还算了解,推荐使用此方法,一步搞定。

1
$.getJSON("http://xxxxxxxxxxx/xxxxxxxx/xxxxxx/list?callback=?","name=zhangsan&id=10",function (json){})

这个方法jquery会自动判断是否跨域,如果跨域,则jquery会以jQuery1345xxxxxxxxxxx的形式自动填充callback,后端获取到这一串名,直接执行,你在回调函数就能收到后端返回的数据

第二个部分传递的data可以按照官方格式(也就是name=xx&id=xx)来传递,也可以以json形势传递。

$.ajax

这个方法为什么放到第二个来说?主要是涉及到的几个参数需要与第一种方法对比解释:

1
2
3
4
5
6
7
8
9
10
$.ajax({
url:"http://xxxxxxxxxxxxxxx",
type:"get",    //必须是get,jsonp跨域只支持get方法。
datatype:"jsonp",
jsonp:"callback", //这个jsonp是什么意思?看到第一种方法的callback没?如果你不设置jsonp,那么默认回调函数参数名就是callback,但是最好别改,因为这是向服务器发送请求的函数参数名,服务端拿到该参数的值value后以"value({\"userid\":0,\"username\":\"null\"})"的形式返回给客户端数据
jsonpcallback:"mycallback",//这里的jsonpcallback就是回调函数名,我在第一种方法中说道过,使用getJSON时,回调函数的名字由query自动填充,如果设置了就是按照你的名字返回。即可以在success方法外调用该函数获得返回值
success:function(json){    
//do something  
    }
})

跨域请求文件

有些时候我们可能需要跨域请求一些文件,那么这个时候就没有后端的童鞋给你合作,显然我们jQuery请求接口的方式不起作用了。那么这个时候就需要用到咱们jsonp跨域的的原理了。

jsonp本质

是通过script标签请求数据,然后返回的数据再用一个函数来包装。

jsonp原理

因为咱们的src是没有跨域的限制的,所以我们通过script标签跨域请求资源的原理来间接的请求数据。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Jsonp Test</title>
</head>
<body>
<script type="text/javascript">
function getData(data){
console.log(data.name);
}
</script>
</body>
<script type="text/javascript" src="test.json"></script>
</html>

这里我们定义了一个getData的方法,来获取咱们src读取的资源,相当于请求成功的回调函数。

test.json Code

1
getData({"name":"pwh","sex":"23"})

这里咱们用getData把数据包装起来,齐活儿!
执行上述代码,即可实现咱们利用jsonp原理请求文件!

JS基础及基本算法

发表于 2019-02-02 | 分类于 前端 , javascript

js基础

作用域

前言

都知道JS存在三种作用域,即全局,函数,块级。其实是没有块级作用域的,但我们可以通过闭包来实现它。

函数作用域

很好理解,在函数内部声明的变量在函数调用后会销毁

块级作用域

在if,while,for等代码体用{}包括的,成为块级作用域,但是在JS中没这说法。
比如

1
2
3
4
for(var i=0;i<10;i++){
console.log(i);
}
alert(i) // 9

问题出来了,按理说正常情况是控制台输出0~9,弹出undefined,但是alert出来的却是9,

那这足以说明咱们的JS是不存在块级作用域的,块里面的变量是被挂载在window上的。

那么这会引发一个问题,看下面代码:

1
2
3
4
5
6
7
8
9
var arrs=[];
for(var i=0;i<10;i++){
arrs[i]=function(){
console.log(i);
}
}
arrs[0] // 9
arrs[1] // 9
//..........

这里你不管执行 第几个函数都是打印9,原因有两个,第一因为JS中不存在块级作用域,i的声明是挂载在window上的,再一个,函数在执行的时候获取到的i为循环最后一次+1的i,所以恒定为9。
解决办法:利用闭包虚构一个块级作用域,并将i的值传入。

闭包

注意事项:

  • 函数的传参是传入的复制值
  • 闭包其实就是能获取到函数内部变量的函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var a1 = function(){
    var n = 999;
    a2 = function(){
    n++;
    }
    return n;
    }
    console.log(a1()); // 999
    a2();
    console.log(a1()); // 1000

闭包的两个主要作用:

  • 获取函数内部的变量
  • 使函数内部的变量不被回收
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var arrs=[];
    for(var i=0;i<10;i++){
    (function(i){
    arrs[i]=function(){
    console.log(i);
    }
    })(i) //将循环的i的值传入
    }
    arrs[0] // 0
    arrs[1] // 1
    //..........

此时当函数执行的时候会拿到这个立即执行函数的i的值,而立即执行函数的值是for循环传入的,所以就能正常输出0~9。

JavaScript数据类型

分为基本类型和引用类型
值类型(基本类型):
字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol
引用数据类型:
对象(Object)、数组(Array)、函数(Function)

判断数据类型的方法

1
var arr = [];

typeof

1
2
console.log(typeof arr)   // object    对于复杂类型的对象只能返回object
console.log(typeof null) // object

instanceof

1
console.log(arr instanceof Array)   // true    可以判断变量的引用类型

Object.prototype.toString.call()

1
Object.prototype.toString.call(arr) == "[object Array]";     //基本类型和引用类型都可以得到

undefined和null的区别

null表示”没有对象”,即该处不应该有值。典型用法是:

  • 作为函数的参数,表示该函数的参数不是对象。
  • 作为对象原型链的终点。
  • 释放内存
1
2
3
Object.getPrototypeOf(Object.prototype)    // null
var arr = [1,2,5,41,54,]
arr = null;

undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义。典型用法是:

  • 变量被声明了,但没有赋值时,就等于undefined。
  • 调用函数时,应该提供的参数没有提供,该参数等于undefined。
  • 对象没有赋值的属性,该属性的值为undefined。
  • 函数没有返回值时,默认返回undefined。
1
2
3
4
5
6
7
8
console.log(typeof undefined);  //undefined
console.log(typeof null); //object

console.log(Number(undefined));//NaN
console.log(Number(10+undefined));//NaN

console.log(Number(null));//0
console.log(Number(10+null));//10

Boolean与条件判断

首先明白两点

  • 只有false,””,0,null,NaN,undefined会被转换成false。
  • 任何类型的数据在与布尔值比较时都会被转换为number类型。
1
2
3
console.log(Boolean(false));  //false

console.log(Boolean("false")); //true

其实我们的if语句的条件没有运算符的情况下就是执行了Boolean操作

1
2
3
4
5
if(x){

}
//等价于
if(Boolean(x))

那么能看懂下面的东西就对这块掌握的比较好了

1
2
3
4
5
6
7
8
console.log(Number([]));      //0
console.log([] ? true : false); //true if语句同等效果

console.log(Number({})); //NaN
console.log({} ? true : false); //true if语句同等效果

console.log([] == false); //true
console.log({} == false); //false

Symbol

Symbol是由ES6规范引入的一项新特性,它的功能类似于一种标识唯一性的ID
创建方式:
Symbol()

1
const name = Symbol('username')

特性一:每一次创建的Symbol都是唯一的,就算描述一致

1
console.log(Symbol("username") === Symbol("username"));   //  false

特性二:可以作为对象的属性,只能以[]方式访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const name = Symbol('username');   
let obj = {
[name]: '张三',
age: 18,
sex: 'male'
}
//或者
let obj = {
age: 18,
sex: 'male'
}
obj[name] = '张三';

console.log(obj[name]); //张三
console.log(obj.name); //undefined

特性三:可以被外部访问,但不能被枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const name = Symbol('username');   
let obj = {
[name]: '张三',
age: 18,
sex: 'male'
}
console.log(obj[name]); //Symbol只能以[]方式访问

Object.keys(obj) // 返回一个由obj所有自身属性的属性名(不包括不可枚举属性也不包括Symbol值作为名称的属性)组成的数组 ['age', 'sex']

for (let p in obj) {
console.log(p) // 分别会输出:'age' 和 'sex'
}

Object.getOwnPropertyNames(obj) // 返回一个由obj所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组 ['age', 'sex']

console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(username)]

Reflect.ownKeys(obj) // 返回一个由obj所有自身属性的属性名(包括不可枚举属性和Symbol值作为名称的属性)组成的数组 [Symbol(username), 'age', 'title']

全局Symbol,使用Symbol.for可以保证创建的symbol唯一

1
console.log(Symbol.for("username") === Symbol.for("username"));   //  true

Bind函数

下面是MDN的bind函数实现,这里对下面的一些写法进行剖析。

bind做了什么?

  • 调用bind,就会返回一个新的函数。
  • 新的函数里面的this就指向bind的第一个参数,同时this后面的参数会提前传给这个新的函数。调用该新的函数时,再传递的参数会放到预置的参数后一起传递进新函数。

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}

var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
// this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
return fToBind.apply(this instanceof fBound ?
this :
oThis,
// 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
aArgs.concat(Array.prototype.slice.call(arguments)));
};

// 维护原型关系
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
// 下行的代码使fBound.prototype是fNOP的实例,因此
// 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
fBound.prototype = new fNOP();

return fBound;
};
}
  • Array.prototype.slice.call(arguments, 1)

    我们都知道arguments是一个类数组对象,所以这里调用数组的slice方法给arguments使用。因为arguments能像访问数组的形式进行访问(arguments[0]),且具有length属性,因此我们可以得到返回的一个有length长度的数组。
    后面传入参数1,是slice(start, end)中的一个参数start,表示从arguments的小标为1,即第二个参数开始切割。 这里是将bind函数的参数数组取出来,第一个参数不要(就是不要oThis)也就是要被绑定方法的那个对象

  • fNOP和fBound函数

    1
    2
    3
    4
    5
    //假设bind过程如下
    function a(){
    this.name = 'a';
    }
    var b = a.bind(oThis);
    • 正常调用bind函数
      返回一个新函数,该函数的this指向调用bind方法的一个参数。即代码里的:

      1
      2
      3
      4
      //bind函数内部解析
      fToBind = this; //这里的this就是a;
      fToBind.apply(oThis,........................) //这里便是将oThis替换a函数的this;
      return fBound; //即b的this便是指向oThis;

      到这里正常调用bind并传参就完成了

    • bind返回的函数作为构造函数的情况。首先我们得理解new做了什么,如下:

      1
      2
      3
      4
      5
      var o = new fun();
      //实际操作
      var o = new Object();
      o.__proto__ = Foo.prototype;
      fun.call(o);

      这里可以看到,当fun被当做构造函数执行时,fun的this指向的是o,且o可以访问fun的原型的属性。

1
2
3
4
5
6
fNOP = function () {} ;
if(this.prototype) {
// 规避Function.prototype没有prototype属性
fNOP.prototype = this.prototype; // 构造新函数,将a函数的prototype做一个中转防止原型链污染
}
fBound.prototype = new fNOP();

这样便兼容了新函数作为构造函数的情况。
这里有一个疑问,为什么新函数的要继承a函数的原型,以下是mdn的解释:

bind() 函数会创建一个新绑定函数(bound function,BF)。绑定函数是一个exotic function object(怪异函数对象,ECMAScript 2015中的术语),它包装了原函数对象。调用绑定函数通常会导致执行包装函数。

基本算法

冒泡算法

作为经典入门算法之一,因为其每轮的比较都会确认一个最大(最小)的数,就像冒出来一样而得名。

设计思路:

  • 确定程序执行次数
  • 两两比较,按照规定交换位置

程序实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const arr = [10,5,6,25,1000,1];
function bubbleSort(arr) {
for(let i = 0,len=arr.length;i<len-1;i++) {
for(let j = 0;j<len-1-i;j++) {
if(arr[j]>arr[j+1]) {
let tem = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tem;
}
}
}
return arr;
}
console.log(bubbleSort(arr)) //[1, 5, 6, 10, 25, 1000]

按照上面的数组来说就是
第一轮…….
第一次:10和5比较,交互位置,此时数组为[5,10,6,25,1000,1]
第二次:10和6比较,交换位置,此时数组为[5,6,10,25,1000,1]
…………………………………………..
第length-1次:此时数组为[5,6,10,25,1,1000]
可以看到我们已经确定了一个最大数1000,那么接下来就将[5,6,10,25,1]继续执行第二轮
第二轮…….
第length-1轮…….得到最终排好序的数组

冒泡算法效率低,我们在此基础上尽量的去优化其效率
重点解释两个地方

外层循环次数length-1
  • 按照我们的算法设计逻辑,因为每一次都会有一个数被确定,所以当执行了l-1轮,就剩最后一个数。自然是最小(最大)的,所以无需再执行
    内层循环次数length-1-i
  • 首先一个数组两两比较需要的次数为length-1次,上面我们说到每轮都会确定一个数,那么确定的数无需进行比较所以再-i

选择排序

类似与冒泡,不同的是每轮确定一个最小(最大)的数,再将其依次安放

程序实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const arr = [10,5,6,25,1000,1];
function selectSort(arr) {
const len = arr.length;
let minIndex, temp;
for (var i = 0; i < len - 1; i++) {
minIndex = i; //当前位置设置为最小
for (var j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) { //检查其他位置上有无比设定的最小位置更小的
minIndex = j;
}
}
if(minIndex != i){ //当前位置不是最小值就交换
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
return arr;
}
console.log(selectSort(arr)) //[1, 5, 6, 10, 25, 1000]

快速排序

确定一个中间值,和其他元素进行比较,小的放左边数组,大的放右边数组。再递归左边的数组和右边的数组,最后合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const arr = [10,5,6,25,1000,1];
function quickSort(arr) {
if(arr.length <=1){
return arr;
}
let c = arr[0],leftArr=[],rightArr=[];
for(var i = 1;i<arr.length;i++){
if(arr[i] > c){
rightArr.push(arr[i]);
}else{
leftArr.push(arr[i]);
}
}
return [].concat(quickSort(leftArr),c,quickSort(rightArr));
}
console.log(quickSort(arr)) //[1, 5, 6, 10, 25, 1000]

用Yeoman + gulp + webpack 来管理你的前端项目

发表于 2019-01-11 | 分类于 前端 , node , 构建工具

前言

Yeoman:前端脚手架,快速搭建前端开发环境,优化工作流~
Gulp:工程化构建工具,基于task来处理任务
Webpack:最常见的前端构建工具,类似与gulp,但不如gulp灵活,专注于代码打包编译

OK,主人公们介绍完了,该篇主要说明三个工具的基本用法,安装配置自己解决。

Yeoman

Q:

  • 在写东西的时候经常会遇到一些重复性的操作和代码,苦于Ctrl+c
  • 人数较多的前端团队10个人拥有十种代码风格,十种项目结构,后期维护及其繁琐

——使用Yeoman 达到One Code Style One Directory Structure

安装:

1
cnpm install -g yo

使用:

yeoman 提供很多generator,可以直接使用
1
2
3
mkdir project-name
cd project-name
yo

按照提示选择需要的模板就行了,这里主要说一说怎么私人订制~嘻嘻

创建自己的generator

yeoman官方提供了generator-generator 来帮助我们自定义生成器,良心啊~

1
2
cnpm install -g generator-generator
yo generator

我们需要做的就是在index.js中来创造我们自己的generator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
'use strict';
const Generator = require('yeoman-generator');
const chalk = require('chalk');
const yosay = require('yosay');

module.exports = class extends Generator {
prompting() {
// Have Yeoman greet the user.
this.log(
yosay(`Welcome to the tiptop ${chalk.red('generator-xy')} generator!`)
);

const prompts = [
{
type: 'confirm',
name: 'someAnswer',
message: 'Would you like to enable this option?',
default: true
}
];

return this.prompt(prompts).then(props => {
// To access props later use this.props.someAnswer;
this.props = props;
});
}

writing() {
this.fs.copy(
this.templatePath('dummyfile.txt'),
this.destinationPath('dummyfile.txt')
);
}

install() {
this.installDependencies();
}
};

可以看到核心的流程是在一个继承了generator的类当中
其实这里generator一共提供了 initializing,prompting,configuring,default,writing,conflicts,install,end这几个函数

Prompting()方法是执行前的过渡,实现与用户的交互,你可以在prompts问题集中定义一些问题,比如你叫啥,干啥,弄啥,家里几口人,人均几亩地………..然后将用户输入的内容保存在this.props中,方便以后调用。

我们自己来写一个generator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
prompting() {
// Have Yeoman greet the user.
this.log(
yosay(`Welcome to the tiptop ${chalk.red('generator-xy')} generator!`)
);
return this.prompt([{
type : 'input',
name : 'appname',
message : 'input your project name',
default : this.appname
}
]).then((answers) => {
this.log('appname :', answers.appname);
this.props = answers;
})
}

在后面的方法中,我们便可以通过this.props.appname来获取到输入的项目名称

我们在原来的基础上定义一个defaults方法来生成用户输入的目录

1
2
3
4
5
6
7
8
9
10
11
12
13
const path = require('path');
const mkdirp = require('mkdirp');

defaults () {
if (path.basename(this.destinationPath()) !== this.props.appname) { //判断是否存在该目录
this.log(
'Your generator must be inside a folder named ' + this.props.appname + '\n' +
'I\'ll automatically create this folder.'
);
mkdirp(this.props.appname); // 即 mkdir -p 创建该目录
this.destinationRoot(this.destinationPath(this.props.appname));
}
}

writing

writing方法用来书写创建工程文件的步骤
在这之前我们首先在template文件夹下创建一个public目录,里面创建如下文件作为咱们这个初级教程的全部内容

开始写writing方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
writing() {
mkdirp('css'); //创建css文件夹
mkdirp('js'); //创建js文件夹
this.fs.copy( //调用方法将模板的内容创建到根目录
this.templatePath('public/index.html'), //模板文件地址
this.destinationPath('index.html'), //创建在根目录
),

this.fs.copy(
this.templatePath('public/index.css'),
this.destinationPath('css/index.css')
);

this.fs.copy(
this.templatePath('public/index.js'),
this.destinationPath('js/index.js')
);
}

install

最后install方法,官方的api说的很清楚this.installDependencies(),即是用来安装我们项目依赖的

1
2
3
install() {
this.npmInstall(['jquery'], { 'save-dev': true });
}

这里就安装一个jquery作为说明即可。
最后我们在根目录执行

1
$ npm link

这样我们就可以在全局使用该generator了

然后切换到开发目录,执行

1
$ yo xy

然后你就可以开始编码了,so easy今后所有这种类型的项目一个命令几秒钟就可以开始愉快的编码,而且代码风格统一~

Gulp+Webpack

这里把Gulp和Webpack放到一起来说。
博主最早是只用了webpack来构建自己的项目,后来加入Gulp对其进行整合,发现配合食用,口感更佳呀
核心依然是plugin

Webpack出口文件即Gulp入口文件

这里也只是讲如何写一个初略的gulpfile.js

核心便是task,src,start,watch等api,详细说明见官网Gulp Api
基本工作流程:

  • 通过gulp.src()方法获取到我们想要处理的文件流(Vinyl files 的 stream),
  • 把文件流通过pipe方法导入到gulp的插件中进行处理,比如调用concat方法合并所有css,再调用minify()压缩css。(具体插件用法,npm官网均有介绍)
  • 把处理后的流再通过pipe方法导入到gulp.dest()中,最后把流中的内容写入到文件中

gulpfile.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

//加载外挂:自动瞄准,无后座,锁血,大挪移.......~~~
var gulp = require('gulp'),
minify=require('gulp-minify-css');
autoprefixer = require('gulp-autoprefixer'),
jshint = require('gulp-jshint'),
uglify = require('gulp-uglify'),
imagemin = require('gulp-imagemin'),
clean = require('gulp-clean'),
concat = require('gulp-concat'),
notify = require('gulp-notify'),
cache = require('gulp-cache'),
livereload = require('gulp-livereload'),
fileinclude = require('gulp-file-include'),
webpack = require('gulp-webpack');

gulp.task('css', function() {
gulp.src('src/css/*.css')
.pipe(concat('main.css'))
.pipe(minify())
.pipe(gulp.dest('dist/css'));
})

gulp.task('scripts', function() {
return gulp.src('src/entry.js')
.pipe(webpack( require('./webpack.config.js') ))
.pipe(gulp.dest('dist/js'));
});

gulp.task('images', function() {
return gulp.src('src/images/**/*')
.pipe(cache(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true })))
.pipe(gulp.dest('dist/images'))
.pipe(notify({ message: 'Images compile complete' }));
});

gulp.task('html', function() {
return gulp.src('src/**/*.html')
.pipe(fileinclude({
prefix: '@@',
basepath: '@file'
}))
.pipe(gulp.dest('dist/'))
.pipe(notify({ message: 'html compile complete' }));
});

gulp.task('clean', function() {
return gulp.src(['dist/css', 'dist/js', 'dist/images'], {read: false})
.pipe(clean());
});

gulp.task('default', ['clean'], function() {
gulp.start('css','scripts', 'images', 'html');
});


gulp.task('watch', function() {

gulp.watch('src/css/**/*.css', ['css']);

gulp.watch('src/js/**/*.js', ['scripts']);

gulp.watch('src/images/**/*', ['images']);

gulp.watch('src/**/*.html', ['html']) ;

livereload.listen();
gulp.watch(['dist/**']).on('change', livereload.changed);

});
  • 插件的话按需自取,这里我用的插件是包含了处理所有文件的。可以酌情增减
  • gulp.task第一个参数为任务名,gulp task-name 即可执行对应的任务,这里需要解释的就是对于js的处理。刚才说过webpack的出口文件就是gulp的入口文件,这里我们用到了gulp-webpack包来优化。
  • 在默认任务这里执行编译之前调用gulp.clean清空上一次的编译结果
  • 这里使用了livereload插件,需要配置Chrome(美中不足,显然没有webpack-dev-server实在啊)
  • 运行
    1
    2
    gulp
    gulp watch

小谈移动端自适应

发表于 2019-01-03 | 分类于 前端 , css

前言

目前的移动端适配大致分为两个流派
1.通过media去做不同屏幕的css hack
2.Rem高清适配方案

Media

1
2
3
@media only screen and (min-width: 320px) and (max-width: 639px){
.....
}

缺陷

  • 在大屏手机会觉得页面稍小,小屏手机页面稍大
  • 如果做复杂一点的页面@media查询会非常的多
  • Dom上的元素如果要和背景图片结合位置不好确定

Rem

基于Rem的适配方法,动态改变1rem的值(html font-size的值)来达到适配。这种适配方法也有两种类型,分别以网易和淘宝为例(其实差别不大)

重要:在非根元素下1rem的值相当于根元素的font-size大小。rem适配的原理是等比缩放!!!一般根据宽度

网易流程如下:

  • 确定基础稿,假设基础稿宽度为640px。

  • 在640px的基础稿下,假设一个按钮的宽度为150px,刚才我们说了,rem适配的原理是等比缩放。那么怎么去等比,我们就借助rem这个媒介,我们还提到一般是根据宽度去做缩放,那么我们把rem的计算和宽度建立上联系不就行了?网易的做法是在计算1rem值的时候根据屏幕宽度比来计算。

  • 我们设置在640px的基础稿下1rem = 100px。那么这个按钮的宽度就应该为1.5rem(为什么设置100?其实任何值都行,只是100方便与与计算)。

  • 那么这个视觉稿做成的网页在其他屏幕上怎么去适配呢?我们不知道其他屏幕到底多大呀~这就是我们设置基础稿的原因。再一次强调rem的原理是等比缩放。那么在640的屏幕下1.5rem为按钮的宽度,那么当网页到了320px的屏幕下呢?根据等比缩放原则=》640/320 = 100 / 当前页面1rem的值。

代码实现也就是:

1
document.documentElement.style.fontSize = deviceWidth / 6.4 + 'px';

这样在没个页面安装基准稿等比缩放算出来的1rem的值就能达到页面的适配

淘宝流程如下:

  • 前期步骤和网易一样都是设置先设置基准稿

  • 淘宝还考虑了dpr的影响,一般基准稿是基于iphone的,而iphone这种retina屏幕的dpr为2

  • 淘宝的做法就是动态的去设置meta来缩放,以达到高清适配的目的

    1
    meta.setAttribute('content','initial-scale='+1/dpr +', maximum-scale='+1/dpr +', minimum-scale='+1/dpr +', user-scalable=no');

    这里对页面进行了缩放,就会涉及到图片失帧的问题,所以得用2倍图3倍图写css hack解决,还有一点就是页面根据dpr做了缩放,所以在计算1rem的值的时候得乘上dpr值

  • 那么这里计算rem与网易的区别就出来了,网易是在计算1rem值的时候根据屏幕宽度比来计算,而淘宝的1rem值都是屏幕宽的10份,它在计算1rem的值的时候其实是将元素宽度和屏幕宽度建立联系来计算的。所以淘宝的高清适配方案放在网易上也是可行的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var dpr = window.devicePixelRatio || 1, scale = 1 / dpr;  

    var docEle = document.documentElement;

    var deviceWidth = docEle.clientWidth;

    var metaEle = document.querySelector('meta[name="viewport"]');

    metaEle.setAttribute('content', 'width=' + dpr * deviceWidth + ',initial-scale=' + scale + ',maximum-scale=' + scale + ', minimum-scale=' + scale + ',user-scalable=no');

    docEle.setAttribute('data-dpr', dpr); //css hack

    if (deviceWidth > 750) deviceWidth = 750;

    document.documentElement.style.fontSize = deviceWidth * dpr / 7.5 + 'px';
  • 接下来就是计算1rem的值,也就是 基准值(1rem=clientwidth*dpr/10。这里说过任何值都行)

  • 之后布局所用到的位置单位全部用Rem代替(这就和网易的不同了。若有一个750px的div,假设我们算出来的1rem的为50,那么该div的宽度就该写为15rem)转换公式rem=px/ 基准值,这里如果你自己用这种方法的话会发现当你要将px转化为rem时,计算会很麻烦,但是前端构建的强大,less,sass能轻松解决。

上一页12

Irwin

web技术探讨总结,javascript疑难,nodejs应用,微信小程序,react-native实战,网络通信。

20 日志
15 分类
36 标签
GitHub E-Mail
© 2020 Irwin
Power By Hexo | NexT