分类目录归档:前端

前端包括Javascript和css等

css实现响应式正方形或固定宽高比


代码如下
HTML:

<div class="square">
  <div class="content">
    Hello!
  </div>
</div>

css:

.square {
  position: relative;
  width: 50%;
  border: 4px solid red;
}
.square:after {
  content: "";
  display: block;
  padding-bottom: 100%;
}
.content {
  position: absolute;
  width: 100%;
  height: 100%;
}

See the Pen jxXeEB by Qing Sheng (@shengoo) on CodePen.

原理如下

.square中创建一个伪元素,使用padding-bottom: 100%;,伪元素会用父级节点的宽度来计算100%,所以高度就是父级的宽度,这样就能实现正方形来。
同理,需要固定宽高比的情况下,都可以使用这种方式。

使用create-react-app生成react多页面应用

  1. 初始化react app
    npx create-react-app multiple-page-app
    
  2. eject(eject前要commit)
    yarn eject
    
  3. 在src文件夹里新建一个about.css(假如我们要做的另一个页面是about.html)
    body{
        background-color: yellow;
    }
    
  4. 在src文件夹里新建一个about.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import './about.css';
    ReactDOM.render(<div>about</div>, document.getElementById('root'));
    
  5. 增加入口配置,config/webpack.config.dev.js
    entry: {
        index:[
            require.resolve('./polyfills'),
            require.resolve('react-dev-utils/webpackHotDevClient'),
            paths.appIndexJs
        ],
        about: [
            require.resolve('./polyfills'),
            require.resolve('react-dev-utils/webpackHotDevClient'),
            paths.appSrc + "/about.js",
        ]
    },
    
  6. 修改输出配置output选项
    filename: 'static/js/[name].bundle.js',
    
  7. 增加HtmlWebpackPlugin
    new HtmlWebpackPlugin({
        inject: true,
        chunks: ["index"],
        template: paths.appHtml,
    }),
    new HtmlWebpackPlugin({
        inject: true,
        chunks: ["about"],
        template: paths.appHtml,
        filename: 'about.html',
    }),
    
  8. 效果如下

  9. 然后按照对dev.js的修改,同样修改好prod.js,就可以build出两个页面了。

完整代码可以在github上看到:
https://github.com/shengoo/react-demo/tree/master/multiple-page-app

在create-react-app中使用Code Splitting

在create-react-app中使用Code Splitting实现按需加载js

为了减少HTTP请求,我们会把代码打包到一个文件里。
Code splitting可以把打包的文件分割成不同的块,并实现按需加载。
下面是在create-react-app中使用方法。

简单示例

  1. 创建项目
    npx create-react-app code-splitting
    
  2. 在src文件夹里创建文件texts.js
    const hello = 'Hello World!';
    export { hello };
    
  3. 修改App.js
    class App extends Component {
        constructor(props){
            super(props);
            this.state = {};
        }
        componentDidMount(){
            import('./texts')
                .then(({hello}) => {
                    this.setState({
                        msg: hello,
                    })
                })
                .catch(err => {
                });
        }
        render() {
            return (
                <div className="App">
                    <div>{this.state.msg || 'loading...'}</div>
                </div>
            );
        }
    }
    export default App;
    
  4. Chrome中network

分割react的组件

使用React Loadable分割,React Loadable还能创建 loading states, error states, timeouts, preloading,等状态。
原来的引用方式:

import OtherComponent from './OtherComponent';
const MyComponent = () => (
    <OtherComponent/>
);

使用React Loadable启用了Code splitting的方式:

import Loadable from 'react-loadable';
const LoadableOtherComponent = Loadable({
    loader: () => import('./OtherComponent'),
    loading: () => <div>Loading...</div>,
});
const MyComponent = () => (
    <LoadableOtherComponent/>
);

例子:

  1. 添加依赖
    yarn add react-loadable

  2. 新建一个组件Hello.js

    import React from 'react';
    export default () => (
        <div>Hello world.</div>
    )
    
  3. 在App.js中引用Hello
    import Loadable from 'react-loadable';
    const LoadableOtherComponent = Loadable({
        loader: () => import('./Hello'),
        loading: () => <div>Loading...</div>,
    });
    const Hello = () => (
        <LoadableOtherComponent/>
    );
    
  4. 在render函数里渲染

  5. 从network里可以看到又多了一个js文件

根据路由分割

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import Loadable from 'react-loadable';
const Loading = () => <div>Loading...</div>;
const Home = Loadable({
  loader: () => import('./routes/Home'),
  loading: Loading,
});
const About = Loadable({
  loader: () => import('./routes/About'),
  loading: Loading,
});
const App = () => (
  <Router>
    <Switch>
      <Route exact path="/" component={Home}/>
      <Route path="/about" component={About}/>
    </Switch>
  </Router>
);
  1. 安装依赖
    yarn add react-router-dom

  2. 创建路由页面
    routes/Home.js

    import React from 'react';
    export default () => (
        <div>Home</div>
    )
    

    routes/About.js

    import React from 'react';
    export default () => (
        <div>About</div>
    )
    
  3. 在App.js中引用路由页面
    const Loading = () => <div>Loading...</div>;
    const Home = Loadable({
        loader: () => import('./routes/Home'),
        loading: Loading,
    });
    const About = Loadable({
        loader: () => import('./routes/About'),
        loading: Loading,
    });
    
  4. 配置路由
    import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
    ...
    render() {
        return (
            <Router>
                <div>
                    <div>{this.state.msg || 'hello'}</div>
                    <Hello/>
                    <div>
                        <Link to={'/'}>home</Link>
                        <Link to={'/about'}>about</Link>
                    </div>
                    <Switch>
                        <Route exact path="/" component={Home}/>
                        <Route path="/about" component={About}/>
                    </Switch>
                </div>
            </Router>
        );
    }
    
  5. 在Chrome中查看network,可以看到第一次点击about的时候,浏览器会异步加载about组件的js

完整代码可以在github上看到:
https://github.com/shengoo/react-demo/tree/master/code-splitting

react-native中使用SafeAreaView保证iPhoneX兼容性

react-native从0.50.1开始,提供了SafeAreaView来确保iPhone X的兼容性,效果如下:

代码如下:

import {
  ...
  SafeAreaView
} from 'react-native';
class Main extends React.Component {
  render() {
    return (
      <SafeAreaView style={styles.safeArea}>
        <App />
      </SafeAreaView>
    )
  }
}
const styles = StyleSheet.create({
  ...,
  safeArea: {
    flex: 1,
    backgroundColor: '#ddd'
  }
})

并且,SafeAreaView会在接打电话等需要调整状态栏高度的时候自动调整状态栏的高度:

jQuery()函数的4中调用方式

jQuery()函数的4中调用方式

选择元素

$(selector)
$(selector,context)

封装成jQuery对象

$(Element|Document|Window)

创建jQuery对象

$('<img/>')
$('<img/>',{
    src: url
})

传入函数,在文档加载完毕运行

DOMContentLoaded

jQuery(function(){});
$(document).ready(function(){});

使用ES6的Proxy实现简单的双向绑定

ES6的Proxy对象可以用来拦截一个Object对象的属性的修改,这次我们利用它来实现一个简单的双向绑定。
废话不多说,请看代码。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>View to model</title>
</head>
<body>
    <div>
        <!-- 使用'bind-to'的属性来标记这个输入框里的值会对Model里的属性进行修改 -->
        <input bind-to="name" />
        <input bind-to="name" />
    </div>
    <!-- 使用#属性名#来把Model里的值映射到DOM中 -->
    <span>#name#</span>
    <span>#name#</span>
    <button onclick="reset()">reset</button>
    <script type="text/javascript">
        var handler = {
            // 拦截属性的设置,触发试图的更新
            set: function(target, key, value, receiver) {
                target[key] = value;
                updateView(propertyName);
                return Reflect.set(target, key, value);
            },
        };
        // 双向绑定的对象
        var model = new Proxy({}, handler);
        // 双向绑定列表
        var inputs = {},views = {};
        var all = document.all;
        // 遍历所有DOM元素
        for(var i=0,l=all.length;i<l;i++){
            // 找到所有有bind-to的输入框
            if(all[i].getAttribute('bind-to')){
                var dom = all[i];
                var propertyName = dom.getAttribute('bind-to');
                inputs[propertyName] = inputs[propertyName] ? inputs[propertyName] : [];
                views[propertyName] = views[propertyName] ? views[propertyName] : [];
                inputs[propertyName].push(dom);
                dom.addEventListener('change',function function_name(e) {
                    var propertyName = e.target.getAttribute('bind-to');
                    model[propertyName] = e.target.value;
                })
            }
            // 找到所有model驱动的页面元素
            if(all[i].innerHTML && all[i].innerHTML.length>2 &&
                all[i].innerHTML[0] == '#' &&
                all[i].innerHTML[all[i].innerHTML.length-1] == '#'){
                var propertyName = all[i].innerHTML.slice(1,all[i].innerHTML.length-1);
                inputs[propertyName] = inputs[propertyName] ? inputs[propertyName] : [];
                views[propertyName] = views[propertyName] ? views[propertyName] : [];
                views[propertyName].push(all[i]);
            }
        }
        // 更新View
        function updateView(propertyName) {console.log('update ' + propertyName);
            if(views[propertyName]){
                for(var i = 0,l = views[propertyName].length;i < l;i++){
                    views[propertyName][i].innerText = model[propertyName];
                }
            }
            if(inputs[propertyName]){
                for(var i = 0,l = inputs[propertyName].length;i < l;i++){
                    inputs[propertyName][i].value = model[propertyName];
                }
            }
        }
        function reset() {
            for(var name in model){
                model[name] = '';
            }
        }
    </script>
</body>
</html>

react native实现拖拽/拖放

react native实现拖拽/拖放

首先初始化一个react native的项目,cd到你的目录下运行

react-native init rndd

创建一个新的组件./app/components/Draggable.js

import React, {
    Component,
} from 'react';
import {
    StyleSheet,
    Image,
    PanResponder,
    Animated,
} from 'react-native';
class Draggable extends Component{
    constructor(props){
        super(props);
    }
    render(){
        return (
            <Animated.View style={styles.container}>
                <Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
            </Animated.View>
        )
    }
}
export default Draggable;
const styles = StyleSheet.create({
    container: {
        position: 'absolute',
        left: 100,
        top: 100,
    }
});

加上PanResponder

PanResponder的api可以上官网查看PanResponder

componentWillMount() {
    this._panResponder = PanResponder.create({
        onMoveShouldSetResponderCapture: () => true,
        onMoveShouldSetPanResponderCapture: () => true,
        onPanResponderGrant: (e, gestureState) => {
        },
        onPanResponderMove: Animated.event([
        ]),
        onPanResponderRelease: (e, {vx, vy}) => {
        }
    });
}
render(){
    return (
        <Animated.View style={styles.container} {...this._panResponder.panHandlers}>
            <Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
        </Animated.View>
    )
}

响应拖拽事件

先在state中创建一个对象来记录拖拽的记录。

constructor(props) {
    super(props);
    this.state = {
        pan: new Animated.ValueXY()
    };
}

更新panHandler:

componentWillMount() {
    this._panResponder = PanResponder.create({
        onMoveShouldSetResponderCapture: () => true,
        onMoveShouldSetPanResponderCapture: () => true,
        // 设置初始位置
        onPanResponderGrant: (e, gestureState) => {
            this.state.pan.setValue({x: 0, y: 0});
        },
        // 使用拖拽的偏移量来定位
        onPanResponderMove: Animated.event([
            null, {dx: this.state.pan.x, dy: this.state.pan.y},
        ]),
        onPanResponderRelease: (e, {vx, vy}) => {
        }
    });
}

更新render方法

render(){
    // 从state中取出pan
    const { pan } = this.state;
    // 从pan里计算出偏移量
    const [translateX, translateY] = [pan.x, pan.y];
    // 设置transform为偏移量
    const imageStyle = {transform: [{translateX}, {translateY}]};
    return (
        <Animated.View style={[styles.container,imageStyle]} {...this._panResponder.panHandlers}>
            <Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
        </Animated.View>
    )
}

这个时候我们刷新用CMD+R刷新,发现已经可以拖动这个图片了,但是有个问题,第二次拖拽的时候又从原点开始移动,我们接下来解决这个问题。
我们有几个选择:

  1. 让拖动的元素在释放的时候回到原来的位置:
    修改PanResponder的onPanResponderRelease方法

    onPanResponderRelease: (e, {vx, vy}) => {
        Animated.spring(
            this.state.pan,
            {toValue: {x: 0, y: 0}}
        ).start();
    }
    
  2. 让拖动的元素停留在拖拽到的位置:
    修改PanResponder的onPanResponderGrant和onPanResponderRelease方法

    this._panResponder = PanResponder.create({
        onMoveShouldSetResponderCapture: () => true,
        onMoveShouldSetPanResponderCapture: () => true,
        // 设置初始位置
        onPanResponderGrant: (e, gestureState) => {
            this.state.pan.setOffset({
                x: this.state.pan.x._value,
                y: this.state.pan.y._value
            });
            this.state.pan.setValue({x: 0, y: 0});
        },
        // 使用拖拽的偏移量来定位
        onPanResponderMove: Animated.event([
            null, {dx: this.state.pan.x, dy: this.state.pan.y},
        ]),
        onPanResponderRelease: (e, {vx, vy}) => {
            this.state.pan.flattenOffset();
        }
    });
    

我们再来加上拖拽时候放大的效果

先在state里加上一个scale用来记录放大的倍数

this.state = {
    pan: new Animated.ValueXY(),
    scale: new Animated.Value(1)
};

我们要在render里使用放大的值

render(){
    // 从state中取出pan和scale
    const { pan, scale } = this.state;
    // 从pan里计算出偏移量
    const [translateX, translateY] = [pan.x, pan.y];
    // 设置transform为偏移量
    const imageStyle = {transform: [{translateX}, {translateY},  {scale}]};
    return (
        <Animated.View style={[styles.container,imageStyle]} {...this._panResponder.panHandlers}>
            <Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
        </Animated.View>
    )
}

在拖拽的时候变大:

onPanResponderGrant: (e, gestureState) => {
    this.state.pan.setOffset({
        x: this.state.pan.x._value,
        y: this.state.pan.y._value
    });
    this.state.pan.setValue({x: 0, y: 0});
    Animated.spring(
        this.state.scale,
        { toValue: 1.3, friction: 3 }
    ).start();
},

在释放的时候缩小到原来的样子:

onPanResponderRelease: (e, {vx, vy}) => {
    this.state.pan.flattenOffset();
    Animated.spring(
        this.state.scale,
        { toValue: 1, friction: 3 }
    ).start();
}

加上拖拽的旋转效果:

先在state中加上一个rotate用来记录旋转的角度

this.state = {
    pan: new Animated.ValueXY(),
    scale: new Animated.Value(1),
    rotate: new Animated.Value(0)
};

在拖拽的时候旋转,需要在onPanResponderGrant里加上

Animated.timing(this.state.rotate, {
    toValue: 25, // 旋转25%,render里interpolate出deg的值
    duration: 300
}).start();

在释放的时候恢复原状,需要在onPanResponderRelease中加上

Animated.timing(this.state.rotate, {
    toValue: 0,
    duration: 300
}).start();

render函数里设置transform:

render(){
    // 从state中取出pan
    const { pan, scale } = this.state;
    // 从pan里计算出偏移量
    const [translateX, translateY] = [pan.x, pan.y];
    // 计算旋转
    const rotate = this.state.rotate.interpolate({
        inputRange: [0, 100],
        outputRange: ['0deg', '360deg']
    });
    // 设置transform为偏移量
    const imageStyle = {transform: [{translateX}, {translateY},  {scale}, {rotate}]};
    return (
        <Animated.View style={[styles.container,imageStyle]} {...this._panResponder.panHandlers}>
            <Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
        </Animated.View>
    )
}

完整代码:

https://github.com/shengoo/rndd

import React, {
    Component,
} from 'react';
import {
    StyleSheet,
    Image,
    PanResponder,
    Animated,
} from 'react-native';
class Draggable extends Component{
    constructor(props){
        super(props);
        this.state = {
            pan: new Animated.ValueXY(),
            scale: new Animated.Value(1),
            rotate: new Animated.Value(0)
        };
    }
    componentWillMount() {
        this._panResponder = PanResponder.create({
            onMoveShouldSetResponderCapture: () => true,
            onMoveShouldSetPanResponderCapture: () => true,
            // 设置初始位置
            onPanResponderGrant: (e, gestureState) => {
                this.state.pan.setOffset({
                    x: this.state.pan.x._value,
                    y: this.state.pan.y._value
                });
                this.state.pan.setValue({x: 0, y: 0});
                Animated.spring(this.state.scale, {
                    toValue: 1.3,
                    friction: 3 }
                ).start();
                Animated.timing(this.state.rotate, {
                    toValue: 25,
                    duration: 300
                }).start();
            },
            // 使用拖拽的偏移量来定位
            onPanResponderMove: Animated.event([
                null, {dx: this.state.pan.x, dy: this.state.pan.y},
            ]),
            onPanResponderRelease: (e, {vx, vy}) => {
                this.state.pan.flattenOffset();
                // Animated.spring(
                //     this.state.pan,
                //     {toValue: {x: 0, y: 0}}
                // ).start();
                Animated.spring(
                    this.state.scale,
                    { toValue: 1, friction: 3 }
                ).start();
                Animated.timing(this.state.rotate, {
                    toValue: 0,
                    duration: 300
                }).start();
            }
        });
    }
    render(){
        // 从state中取出pan
        const { pan, scale } = this.state;
        // 从pan里计算出偏移量
        const [translateX, translateY] = [pan.x, pan.y];
        // 计算旋转
        const rotate = this.state.rotate.interpolate({
            inputRange: [0, 100],
            outputRange: ['0deg', '360deg']
        });
        // 设置transform为偏移量
        const imageStyle = {transform: [{translateX}, {translateY},  {scale}, {rotate}]};
        return (
            <Animated.View style={[styles.container,imageStyle]} {...this._panResponder.panHandlers}>
                <Image style={{width:80,height:80}} source={require('../assets/react-native.jpg')}/>
            </Animated.View>
        )
    }
}
export default Draggable;
const styles = StyleSheet.create({
    container: {
        position: 'absolute',
        left: 100,
        top: 100,
    }
});

使用n管理Node.js版本

Use n to manage your Node.js versions

Installation

  1. Install n through npm:(Recommended)
npm install -g n

Configurations

Since n‘s default path is /usr/local/n, it will need super user’s permission to modify file systems in the default path, we need to config n‘s path to user’s path.

config N_PREFIX

  1. edit bash profile
    vim ~/.bashrc
    
  2. add line
    export N_PREFIX=~/.n
    
  3. apply file
    source ~/.bashrc
    

config PATH

  1. edit bash profile
    vim ~/.bashrc
    
  2. add a line
    export PATH=$HOME/.n/bin:$PATH
    
  3. apply file
    source ~/.bashrc
    

Use

# Use or install a version of node
n 9.0.0
# Use or install the latest official release
n latest
# Use or install the stable official release
n stable
# Use or install the latest LTS official release
n lts

Type n to show list of versions.
And select a version by up down button.

$ n
    node/8.9.3
    node/9.0.0
    node/9.2.1
  ο node/9.3.0

React中的容器组件(Container Component)和展示组件(Presentational Component)

React中的容器组件(Container Component)和展示组件(Presentational Component)

React的思想里,UI=render(data)
所以,React的组件一般是完成两种功能:

  1. 读取数据
  2. 渲染界面

如果我们把两个功能都放在同一个组件内,那么这个组件做的事情就太多了,考虑到组件复用和业务变更,让一个组件只专注做一件事,我们可以把这个组件拆分成多个组件,让每个组件只专注做一件事,所以就衍生了一种React常用的模式:容器组件和展示组件。

容器组件

容器组件在外层,有以下特点:

  • 提供数据
  • 关注业务处理
  • 与状态管理工具交互
  • 有自己的状态(state)
  • 几乎没有dom和样式

展示组件

展示组件在容器组件内部,有以下特点:

  • 关注UI层
  • 有自己等dom和样式
  • 没有自己的状态
  • 不关心数据是如何获取和变化的
  • 通过props接收数据和回调函数
  • 不依赖其他组件
  • 不需要生命周期函数

展示组件一般可以使用纯函数的声明方式:

export default (props)=>(
    <div>Hello {props.name}</div>
);

这样做的好处

  • 分离关注,你可以更好的理解app和UI。
  • 更易复用,同样的展示组件可以在不同的状态源、数据源中使用。也可以封装成容器组件,在未来重用它们。
  • 数据结构和页面结构保持一致性。