分类目录归档:前端

前端包括Javascript和css等

使用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。
  • 更易复用,同样的展示组件可以在不同的状态源、数据源中使用。也可以封装成容器组件,在未来重用它们。
  • 数据结构和页面结构保持一致性。

iOS原生app中集成react-native组件

在iOS原生app中增加ReactNative

  1. 新建一个iOS的项目,打开XCode,新建一个项目,选择Tabbed App

  2. 运行一下看看效果

  3. 新建一个react-native项目,和iOS的项目中同一个目录下

    brew install node
    brew install watchman
    npm install -g react-native-cli
    react-native init ReactNativeProject
    
  4. 启动react-native服务器
    react-native start
    
  5. 安装CocoaPods
    brew install cocoapods
    
  6. 初始化CocoaPods,在你的iOS项目根目录里运行
    pod init
    
  7. 编辑生成的Podfile,增加react-native的pod
    # Uncomment the next line to define a global platform for your project
    # platform :ios, '9.0'
    target 'demo' do
    # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
    use_frameworks!
    # Pods for demo
    pod 'React', :path => ‘../ReactNativeProject/node_modules/react-native', :subspecs => [
    'Core',
    'CxxBridge', # Include this for RN >= 0.47
    'DevSupport', # Include this to enable In-App Devmenu if RN >= 0.43
    'RCTText',
    'RCTNetwork',
    'RCTWebSocket', # needed for debugging
    # Add any other subspecs you want to use in your project
    ]
    pod "yoga", :path => "../ReactNativeProject/node_modules/react-native/ReactCommon/yoga"
    pod 'DoubleConversion', :podspec => '../ReactNativeProject/node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
    pod 'GLog', :podspec => '../ReactNativeProject/node_modules/react-native/third-party-podspecs/GLog.podspec'
    pod 'Folly', :podspec => '../ReactNativeProject/node_modules/react-native/third-party-podspecs/Folly.podspec'
    target 'demoTests' do
    inherit! :search_paths
    # Pods for testing
    end
    target 'demoUITests' do
    inherit! :search_paths
    # Pods for testing
    end
    end
    
  8. 运行pod install安装依赖,得到以下输出:
    $ pod install
    Setting up CocoaPods master repo
    $ /usr/bin/git clone https://github.com/CocoaPods/Specs.git master --progress
    Cloning into 'master'...
    remote: Counting objects: 1799117, done.
    remote: Compressing objects: 100% (377/377), done.
    remote: Total 1799117 (delta 157), reused 35 (delta 35), pack-reused 1798692
    Receiving objects: 100% (1799117/1799117), 500.73 MiB | 320.00 KiB/s, done.
    Resolving deltas: 100% (981561/981561), done.
    Checking out files: 100% (203691/203691), done.
    Setup completed
    Analyzing dependencies
    Fetching podspec for `DoubleConversion` from `../ReactNativeProject/node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`
    Fetching podspec for `Folly` from `../ReactNativeProject/node_modules/react-native/third-party-podspecs/Folly.podspec`
    Fetching podspec for `GLog` from `../ReactNativeProject/node_modules/react-native/third-party-podspecs/GLog.podspec`
    Fetching podspec for `React` from `../ReactNativeProject/node_modules/react-native`
    Fetching podspec for `yoga` from `../ReactNativeProject/node_modules/react-native/ReactCommon/yoga`
    Downloading dependencies
    Installing DoubleConversion (1.1.5)
    Installing Folly (2016.09.26.00)
    Installing GLog (0.3.4)
    Installing React (0.51.0)
    Installing boost (1.59.0)
    Installing yoga (0.51.0.React)
    Generating Pods project
    Integrating client project
    
  9. 打开iOS项目目录下的demo.xcworkspace

  10. 在AppDelegate.swift文件中引入React

    import React
    
  11. 声明react-native组件的UIViewController并加入到tab中
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -&gt; Bool {
    // Override point for customization after application launch.
    var tab = self.window?.rootViewController as! UITabBarController
    let jsCodeLocation = URL(string: "http://localhost:8081/index.bundle?platform=ios")
    let rootView = RCTRootView(
    bundleURL: jsCodeLocation,
    moduleName: "ReactNativeProject",
    initialProperties: nil,
    launchOptions: nil
    )
    let vc = UIViewController()
    vc.view = rootView
    vc.title = "rn"
    tabbar.viewControllers?.append(vc)
    return true
    }
    
  12. 设置允许localhost的http访问

  13. 运行看看效果

代码: https://github.com/shengoo/iOS-rn-h5