【Hybrid开发高级系列】ReactNative(四) —— 基础开发技巧

1 基础开发技巧

1.1 AppRegistry

        AppRegistry模块则是用来告知React Native哪一个组件被注册为整个应用的根容器。你无需在此深究,因为一般在整个应用里AppRegistry.registerComponent这个方法只会调用一次。上面的代码里已经包含了具体的用法,你只需整个复制到index.ios.js或是index.android.js文件中即可运行。

1.2 Props(属性)

        大多数组件在创建时就可以使用各种参数来进行定制。用于定制的这些参数就称为props(属性)。

        以常见的基础组件Image为例,在创建一个图片时,可以传入一个名为source的prop来指定要显示的图片的地址,以及使用名为style的prop来控制其尺寸。

import React, { Component } from 'react';

import { AppRegistry, Image } from 'react-native';


class Bananas extends Component{

  render() {

    let pic = {

      uri:'https://upload.wikimedia.org/wikipedia/commons/d/de/Bananavarieties.jpg'

    };

    return(

        <Image source={pic} style={{width: 193, height: 110}} />      

    );

  }

}

AppRegistry.registerComponent('Bananas', () => Bananas);

        译注:在iOS上使用http链接的图片地址可能不会显示,参见这篇说明修改

        请注意{pic}外围有一层括号,我们需要用括号来把pic这个变量嵌入到JSX语句中。括号的意思是括号内部为一个js变量或表达式,需要执行后取值。因此我们可以把任意合法的JavaScript表达式通过括号嵌入到JSX语句中。

        自定义的组件也可以使用props。通过在不同的场景使用不同的属性定制,可以尽量提高自定义组件的复用范畴。只需在render函数中引用this.props,然后按需处理即可。下面是一个例子:

import React, { Component } from 'react';

import { AppRegistry, Text, View } from 'react-native';


class Greeting extends Component{

  render() {

    return(

        <Text> Hello {this.props.name}!</Text>

    );

  }

}


class LotsOfGreetings extends Component{

  render() {

    return(

        <View style={{alignItems: 'center'}}>      

           <Greeting name='Rexxar' />

            <Greeting name='Jaina' />        

            <Greeting name='Valeera' />        

        </View>

    );

  }

}

AppRegistry.registerComponent('LotsOfGreetings', () => LotsOfGreetings);

        我们在Greeting组件中将name作为一个属性来定制,这样可以复用这一组件来制作各种不同的“问候语”。上面的例子把Greeting组件写在JSX语句中,用法和内置组件并无二致——这正是React体系的魅力所在——如果你想搭建一套自己的基础UI框架,那就放手做吧!

        上面的例子出现了一样新的名为View的组件。View常用作其他组件的容器,来帮助控制布局和样式。

        仅仅使用props和基础的TextImage以及View组件,你就已经足以编写各式各样的UI组件了。要学习如何动态修改你的界面,那就需要进一步学习State(状态)的概念

1.3 State(状态)

        我们使用两种数据来控制一个组件:props和state。props是在父组件中指定,而且一经指定,在被指定的组件的生命周期中则不再改变。对于需要改变的数据,我们需要使用state。

        一般来说,你需要在constructor中初始化state(译注:这是ES6的写法,早期的很多ES5的例子使用的是getInitialState方法来初始化state,这一做法会逐渐被淘汰),然后在需要修改时调用setState方法。

        假如我们需要制作一段不停闪烁的文字。文字内容本身在组件创建时就已经指定好了,所以文字内容应该是一个prop。而文字的显示或隐藏的状态(快速的显隐切换就产生了闪烁的效果)则是随着时间变化的,因此这一状态应该写到state中。

import React, { Component } from 'react';

import { AppRegistry, Text, View } from 'react-native';


class Blink extends Component{

 constructor(props) {

    super(props);

    this.state = { showText: true};


    // 每1000毫秒对showText状态做一次取反操作

    setInterval(()=> {

      this.setState({ showText: !this.state.showText });

    }, 1000);

  }


  render() {

    // 根据当前showText的值决定是否显示text内容

    let display =this.state.showText ? this.props.text : ' ';

    return(

        <Text>{display}</Text>

    );

  }

}


class BlinkApp extends Component{

  render() {

    return(

        <View>      

            <Blink text='I love to blink' />

            <Blink text='Yes blinking is so great' />

            <Blink text='Why did they ever take this out of HTML' />

            <Blink text='Look at me look at me look at me' />

        </View>

    );

  }

}

AppRegistry.registerComponent('BlinkApp', () => BlinkApp);

        实际开发中,我们一般不会在定时器函数(setInterval、setTimeout等)中来操作state。典型的场景是在接收到服务器返回的新数据,或者在用户输入数据之后。你也可以使用一些“状态容器”比如Redux来统一管理数据流(译注:但我们不建议新手过早去学习redux)。

        State的工作原理和React.js完全一致,所以对于处理state的一些更深入的细节,你可以参阅React.Component API。

        看到这里,你可能觉得我们的例子总是千篇一律的黑色文本,太特么无聊了。那么我们一起来学习一下样式吧。

1.4 样式

        在React Native中,你并不需要学习什么特殊的语法来定义样式。我们仍然是使用JavaScript来写样式。所有的核心组件都接受名为style的属性。这些样式名基本上是遵循了web上的CSS的命名,只是按照JS的语法要求使用了驼峰命名法,例如将background-color改为backgroundColor。

        style属性可以是一个普通的JavaScript对象。这是最简单的用法,因而在示例代码中很常见。你还可以传入一个数组——在数组中位置居后的样式对象比居前的优先级更高,这样你可以间接实现样式的继承。

        实际开发中组件的样式会越来越复杂,我们建议使用StyleSheet.create来集中定义组件的样式。比如像下面这样:

import React, { Component } from 'react';

import { AppRegistry, StyleSheet, Text, View } from 'react-native';


class LotsOfStyles extends Component{

  render() {

    return(

      <View>

         <Text style={styles.red}>just red</Text>

          <Text style={styles.bigblue}> just bigblue</Text>

           <Text style={[styles.bigblue, styles.red]}>bigblue, then red</Text>

           <Text style={[styles.red, styles.bigblue]}>red, then bigblue</Text>

       </View>

    );

  }

}


conststyles = StyleSheet.create({

  bigblue: {

    color: 'blue',

    fontWeight: 'bold',

    fontSize: 30,

  },

  red: {

    color: 'red',

  },

});

AppRegistry.registerComponent('LotsOfStyles', () => LotsOfStyles);

        常见的做法是按顺序声明和使用style属性,以借鉴CSS中的“层叠”做法(即后声明的属性会覆盖先声明的同名属性)。

        文本的样式定义请参阅Text组件的文档

1.5 高度与宽度

        组件的高度和宽度决定了其在屏幕上显示的尺寸。

1.5.1 指定宽高

        最简单的给组件设定尺寸的方式就是在样式中指定固定的width和height。React Native中的尺寸都是无单位的,表示的是与设备像素密度无关的逻辑像素点。

import React, { Component } from 'react';

import { AppRegistry, View } from 'react-native';


class FixedDimensionsBasics extends Component{

  render() {

    return(

        <View>      

            <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}}>

            <View style={{width: 100, height: 100, backgroundColor: 'skyblue'}}>

            <View style={{width: 150, height: 150, backgroundColor: 'steelblue'}}>

        </View>

    );

  }

};

// 注册应用(register Component)后才能正确渲染

// 注意:只把应用作为一个整体注册一次,而不是每个组件/模块都注册

AppRegistry.registerComponent('AwesomeProject', () => FixedDimensionsBasics);

        这样给组件设置尺寸也是一种常见的模式,比如要求在不同尺寸的屏幕上都显示成一样的大小。

1.5.2 弹性(Flex)宽高

        在组件样式中使用flex可以使其在可利用的空间中动态地扩张或收缩。一般而言我们会使用flex:1来指定某个组件扩张以撑满所有剩余的空间。如果有多个并列的子组件使用了flex:1,则这些子组件会平分父容器中剩余的空间。如果这些并列的子组件的flex值不一样,则谁的值更大,谁占据剩余空间的比例就更大(即占据剩余空间的比等于并列组件间flex值的比)。

        组件能够撑满剩余空间的前提是其父容器的尺寸不为零。如果父容器既没有固定的width和height,也没有设定flex,则父容器的尺寸为零。其子组件如果使用了flex,也是无法显示的。

import React, { Component } from 'react';

import { AppRegistry, View } from 'react-native';


class FlexDimensionsBasics extends Component{

  render() {

    return(

        // 试试去掉父View中的`flex: 1`。

        // 则父View不再具有尺寸,因此子组件也无法再撑开。

        // 然后再用`height: 300`来代替父View的`flex: 1`试试看?

        <View style={{flex: 1}}>      

            <View style={{flex: 1, backgroundColor: 'powderblue'}} />

            <View style={{flex: 2, backgroundColor: 'skyblue'}} />

            <View style={{flex: 3, backgroundColor: 'steelblue'}} />

        </View>

    );

 }

};

AppRegistry.registerComponent('AwesomeProject', () => FlexDimensionsBasics);

1.6 使用Flexbox布局

        我们在React Native中使用flexbox规则来指定某个组件的子元素的布局。Flexbox可以在不同屏幕尺寸上提供一致的布局结构。

        一般来说,使用flexDirection、alignItems和 justifyContent三个样式属性就已经能满足大多数布局需求。译注:这里有一份简易布局图解,可以给你一个大概的印象。

        React Native中的Flexbox的工作原理和web上的CSS基本一致,当然也存在少许差异。首先是默认值不同:flexDirection的默认值是column而不是row,alignItems的默认值是stretch而不是flex-start,以及flex只能指定一个数字值。

1.6.1 FlexDirection

        在组件的style中指定flexDirection可以决定布局的主轴。子元素是应该沿着水平轴(row)方向排列,还是沿着竖直轴(column)方向排列呢?默认值是竖直轴(column)方向。

import React, { Component } from 'react';

import { AppRegistry, View } from 'react-native';


class FlexDirectionBasics extends Component{

  render() {

    return(

        // 尝试把`flexDirection`改为`column`看看

        <View style={{flex: 1, flexDirection: 'row'}}>      

            <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />

            <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />        

            <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />

        </View>        

    );

  }

};

AppRegistry.registerComponent('AwesomeProject', () => FlexDirectionBasics);

1.6.2 JustifyContent

        在组件的style中指定justifyContent可以决定其子元素沿着主轴的排列方式。子元素是应该靠近主轴的起始端还是末尾段分布呢?亦或应该均匀分布?对应的这些可选项有:flex-start、center、flex-end、space-around以及space-between。

import React, { Component } from 'react';

import { AppRegistry, View } from 'react-native';


class JustifyContentBasics extends Component{

  render() {

    return(

        // 尝试把`justifyContent`改为`center`看看

        // 尝试把`flexDirection`改为`row`看看

        <View style={{

            flex: 1,

            flexDirection: 'column',

            justifyContent: 'space-between',

         }}>

            <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />

            <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />        

            <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />

        </View>        

    );

  }

};

AppRegistry.registerComponent('AwesomeProject', () => JustifyContentBasics);

1.6.3 AlignItems

        在组件的style中指定alignItems可以决定其子元素沿着次轴(与主轴垂直的轴,比如若主轴方向为row,则次轴方向为column)的排列方式。子元素是应该靠近次轴的起始端还是末尾段分布呢?亦或应该均匀分布?对应的这些可选项有:flex-start、center、flex-end以及stretch。

        注意:要使stretch选项生效的话,子元素在次轴方向上不能有固定的尺寸。以下面的代码为例:只有将子元素样式中的width: 50去掉之后,alignItems: 'stretch'才能生效。

import React, { Component } from 'react';

import { AppRegistry, View } from 'react-native';


class AlignItemsBasics extends Component{

  render() {

    return(

        // 尝试把`alignItems`改为`flex-start`看看

        // 尝试把`justifyContent`改为`flex-end`看看

        // 尝试把`flexDirection`改为`row`看看

        <Viewstyle={{

            flex: 1,

            flexDirection:'column',

            justifyContent:'center',

            alignItems:'center',

          }}>

             <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />

             <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />   

            <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />

        </View>        

    );

  }

};

AppRegistry.registerComponent('AwesomeProject', () => AlignItemsBasics);

1.6.4 深入学习

        以上我们已经介绍了一些基础知识,但要运用好布局,我们还需要很多其他的样式。对于布局有影响的完整样式列表记录在这篇文档中

        现在我们已经差不多可以开始真正的开发工作了。哦,忘了还有个常用的知识点:如何使用TextInput组件来处理用户输入

1.7 处理文本输入

       TextInput是一个允许用户输入文本的基础组件。它有一个名为onChangeText的属性,此属性接受一个函数,而此函数会在文本变化时被调用。另外还有一个名为onSubmitEditing的属性,会在文本被提交后(用户按下软键盘上的提交键)调用。

       假如我们要实现当用户输入时,实时将其以单词为单位翻译为另一种文字。我们假设这另一种文字来自某个吃货星球,只有一个单词: 🍕。所以"Hello there Bob"将会被翻译为"🍕🍕🍕"。

import React, { Component } from 'react';

import { AppRegistry, Text, TextInput, View } from 'react-native';


class PizzaTranslator extends Component{

  constructor(props) {

    super(props);

    this.state = {text: ''};

  }


  render() {

    return(

        <View style={{padding: 10}}>      

            <TextInput

                style={{height: 40}}

                placeholder="Type here to translate!"

                onChangeText={(text) => this.setState({text})}

            />

            <Text style={{padding: 10, fontSize: 42}}>

                 {this.state.text.split(' ').map((word) => word && ' . ').join(' ')}

            </Text>

        </View>

    );

  }

}

// 注册应用(register Component)后才能正确渲染

// 注意:只把应用作为一个整体注册一次,而不是每个组件/模块都注册

AppRegistry.registerComponent('PizzaTranslator', () =>PizzaTranslator);

        在上面的例子里,我们把text保存到state中,因为它会随着时间变化。

        文本输入方面还有很多其他的东西要处理。比如你可能想要在用户输入的时候进行验证,在React的表单组件中的受限组件一节中有一些详细的示例(注意react中的onChange对应的是rn中的onChangeText)。此外你还需要看看TextInput的文档

        TextInput可能是天然具有“动态状态”的最简单的组件了。下面我们来看看另一类控制布局的组件,先从ScrollView开始学习

1.8 网络请求

        很多移动应用都需要从远程地址中获取数据或资源。你可能需要给某个REST API发起POST请求以提交用户数据,又或者可能仅仅需要从某个服务器上获取一些静态内容——以下就是你会用到的东西。新手可以对照这个简短的视频教程加深理解。

1.8.1 使用Fetch

        React Native提供了和web标准一致的Fetch API,用于满足开发者访问网络的需求。如果你之前使用过XMLHttpRequest(即俗称的ajax)或是其他的网络API,那么Fetch用起来将会相当容易上手。这篇文档只会列出Fetch的基本用法,并不会讲述太多细节,你可以使用你喜欢的搜索引擎去搜索fetch api关键字以了解更多信息。

1.8.2 发起网络请求

        要从任意地址获取内容的话,只需简单地将网址作为参数传递给fetch方法即可(fetch这个词本身也就是获取的意思):

fetch('https://mywebsite.com/mydata.json')

        Fetch还有可选的第二个参数,可以用来定制HTTP请求一些参数。你可以指定header参数,或是指定使用POST方法,又或是提交数据等等:

fetch('https://mywebsite.com/endpoint/', {

  method: 'POST',

  headers: {

    'Accept': 'application/json',

    'Content-Type': 'application/json',

  },

  body: JSON.stringify({

    firstParam: 'yourValue',

    secondParam: 'yourOtherValue',

  })

})

        译注:如果你的服务器无法识别上面POST的数据格式,那么可以尝试传统的form格式,示例如下:

fetch('https://mywebsite.com/endpoint/', {

  method: 'POST',

  headers: {

    'Content-Type': 'application/x-www-form-urlencoded',

  },

  body: 'key1=value1&key2=value2'

})

        可以参考Fetch请求文档来查看所有可用的参数。

1.8.3 处理服务器的响应数据

        上面的例子演示了如何发起请求。很多情况下,你还需要处理服务器回复的数据。

        网络请求天然是一种异步操作(译注:同样的还有asyncstorage,请不要再问怎样把异步变成同步!无论在语法层面怎么折腾,它们的异步本质是无法变更的。异步的意思是你应该趁这个时间去做点别的事情,比如显示loading,而不是让界面卡住傻等)。Fetch方法会返回一个Promise,这种模式可以简化异步风格的代码(译注:同样的,如果你不了解promise,建议使用搜索引擎补课):

  getMoviesFromApiAsync(){

    return fetch('http://facebook.github.io/react-native/movies.json')

     .then((response) => response.json())

     .then((responseJson) => {

        return responseJson.movies;

      })

     .catch((error) => {

        console.error(error);

      });

  }

        你也可以在React Native应用中使用ES7标准中的async/await语法:

  // 注意这个方法前面有async关键字

  async getMoviesFromApi() {

    try{

          // 注意这里的await语句,其所在的函数必须有async关键字声明

          let response = await fetch('http://facebook.github.io/react-native/movies.json');

          let responseJson = awaitresponse.json();

          return responseJson.movies;

    }catch(error) {

       console.error(error);

    }

  }

        别忘了catch住fetch可能抛出的异常,否则出错时你可能看不到任何提示。

        默认情况下,iOS会阻止所有非HTTPS的请求。如果你请求的接口是http协议,那么首先需要添加一个App Transport Securty的例外,或者干脆完全禁用ATS,详细可参考这篇帖子

1.8.4 使用其他的网络库

        React Native中已经内置了XMLHttpRequest API(也就是俗称的ajax)。一些基于XMLHttpRequest封装的第三方库也可以使用,例如frisbee或是axios等。但注意不能使用jQuery,因为jQuery中还使用了很多浏览器中才有而RN中没有的东西(所以也不是所有web中的ajax库都可以直接使用)。

var request = newXMLHttpRequest();

request.onreadystatechange = (e) => {

  if(request.readyState !== 4) {

    return;

  }


  if(request.status === 200) {

    console.log('success', request.responseText);

  }else{

    console.warn('error');

  }

};


request.open('GET', 'https://mywebsite.com/endpoint/');

request.send();

        需要注意的是,安全机制与网页环境有所不同:在应用中你可以访问任何网站,没有跨域的限制。

1.8.5 WebSocket支持

        React Native还支持WebSocket,这种协议可以在单个TCP连接上提供全双工的通信信道。

var ws = new WebSocket('ws://host.com/path');

ws.onopen = () => {

  // 打开一个连接

  ws.send('something'); // 发送一个消息

};


ws.onmessage = (e) => {

  // 接收到了一个消息

  console.log(e.data);

};


ws.onerror = (e) => {

  // 发生了一个错误

  console.log(e.message);

};


ws.onclose = (e) => {

  // 连接被关闭了

  console.log(e.code, e.reason);

};

        现在你的应用已经可以从各种渠道获取数据了,那么接下来面临的问题多半就是如何在不同的页面间组织和串联内容了。要管理页面的跳转,你需要学习使用导航器

1.9 使用导航器跳转页面

        移动应用很少只包含一个页面。从你添加第二个页面开始,就得考虑如何管理多个页面间的跳转了。

        导航器正是为此而生。它可以管理多个页面间的跳转,也包含了一些常见的过渡动画,包括水平翻页、垂直弹出等等。

1.9.1 Navigator

        React Native目前有几个内置的导航器组件,一般来说我们首推Navigator。它使用纯JavaScript实现了一个导航栈,因此可以跨平台工作,同时也便于定制。

1.9.2 场景(Scene)的概念与使用

        无论是View中包含Text,还是一个排满了图片的ScrollView,渲染各种组件现在对你来说应该已经得心应手了。这些摆放在一个屏幕中的组件,就共同构成了一个“场景(Scene)”。

        场景简单来说其实就是一个全屏的React组件。与之相对的是单个的Text、Image又或者是你自定义的什么组件,仅仅占据页面中的一部分。你其实已经不知不觉地接触到了场景——在前面的教程中,“编写HelloWorld”“使用Flexbox布局”“如何使用ListView”中的组件都是完整的场景示例。

        下面我们来定义一个仅显示一些文本的简单场景。创建一个名为“MyScene.js”的文件,然后粘贴如下代码:

import React, { Component } from 'react';

import { View, Text } from 'react-native';


export default class MyScene extends Component{

  static defaultProps = {

    title: 'MyScene'

  };


  render() {

    return(

        <View>

            <Text>Hi! My name is {this.props.title}.</Text>

        </View>

    )

  }

}

        注意组件声明前面的export default关键字。它的意思是导出(export)当前组件,以允许其他组件引入(import)和使用当前组件,就像下面这样(下面的代码你可以写在index.ios.js或是index.android.js中):

import React, { Component } from 'react';

import { AppRegistry } from 'react-native';


// ./MyScene表示的是当前目录下的MyScene.js文件,也就是我们刚刚创建的文件

// 注意即便当前文件和MyScene.js在同一个目录中,"./"两个符号也是不能省略的!

// 但是.js后缀是可以省略的

import MyScene from './MyScene';


class YoDawgApp extends Component{

  render() {

    return(

        <MyScene />

    )

  }

}


AppRegistry.registerComponent('YoDawgApp', () =>YoDawgApp);

        我们现在已经创建了只有单个场景的App。其中的MyScene同时也是一个可复用的Reac组件的例子。

1.9.3 使用Navigator

        场景已经说的够多了,下面我们开始尝试导航跳转。首先要做的是渲染一个Navigator组件,然后通过此组件的renderScene属性方法来渲染其他场景。

render() {

  return(

    <Navigator

         initialRoute={{ title: 'My Initial Scene', index: 0 }}

         renderScene={(route, navigator) => {

        return <MyScene title={route.title} />

      }}

    />

  );

}

        使用导航器经常会碰到“路由(route)”的概念。“路由”抽象自现实生活中的路牌,在RN中专指包含了场景信息的对象。renderScene方法是完全根据路由提供的信息来渲染场景的。你可以在路由中任意自定义参数以区分标记不同的场景,我们在这里仅仅使用title作为演示。

1.9.4 将场景推入导航栈#

        要过渡到新的场景,你需要了解push和pop方法。这两个方法由navigator对象提供,而这个对象就是上面的renderScene方法中传递的第二个参数。 我们使用这两个方法来把路由对象推入或弹出导航栈。

navigator.push({

  title: 'Next Scene',

  index: 1,

});

navigator.pop();

下面是一个更完整的示例:

import React, { Component } from 'react';

import { AppRegistry, Navigator, Text, View } from 'react-native';


import MyScene from './MyScene';


class SimpleNavigationApp extends Component{

  render() {

    return(

        <Navigator

           initialRoute={{ title: 'My Initial Scene', index: 0 }}

           renderScene={(route, navigator) =>

            <MyScene

               title={route.title}

               //Function to call when a new scene should be displayed          

               onForward={ () => {   

                  const nextIndex = route.index + 1;

                  navigator.push({

                       title: 'Scene ' + nextIndex,

                       index: nextIndex,

                  });

            }}

            //Function to call to go back to the previous scene

           onBack={() => {

              if(route.index > 0) {

               navigator.pop();

              }

            }}

          />

        }

      />

    )

  }

}

AppRegistry.registerComponent('SimpleNavigationApp', ()=> SimpleNavigationApp);

        对应的MyScene.js代码如下:

import React, { Component, PropTypes } from 'react';

import { View, Text, TouchableHighlight } from 'react-native';


export default class MyScene extends Component{

  staticpropTypes = {

    title: PropTypes.string.isRequired,

    onForward: PropTypes.func.isRequired,

    onBack: PropTypes.func.isRequired,

  }

  render() {

    return(

        <View>

            <Text>Current Scene: { this.props.title }</Text>

            <TouchableHighlight onPress={this.props.onForward}>

               <Text>点我进入下一场景</Text>

            </TouchableHighlight>

            <TouchableHighlight onPress={this.props.onBack}>

              <Text>点我返回上一场景</Text>

            </TouchableHighlight>

        </View>

    )

  }

}

        在这个例子中,MyScene通过title属性接受了路由对象中的title值。它还包含了两个可点击的组件TouchableHighlight,会在点击时分别调用通过props传入的onForward和onBack方法,而这两个方法各自调用了navigator.push()和navigator.pop(),从而实现了场景的变化。

        查看Navigator API文档来了解更多Navigator的信息。同时推荐你阅读导航器对比和论坛中的一个详细教程来加深理解。

1.10 启动运行

1.10.1 纯RN工程配置

1、创建工程

$ react-native init AwesomeProject


2、生成Packager

$ npm start


3、运行原生工程,生成安装包

1.10.2 原生应用嵌入RN页面

1、依赖包安装

npm install


2、原生依赖包安装

$ pod init

编辑Podfile

$ pod install


3、创建一个空的index.ios.js文件

$ touch index.ios.js


4、在index.ios.js中添加你自己的组件


5、运行Packager

$ npm start


1.11 调试

1.11.1 应用内的错误与警告提示(红屏和黄屏)

        红屏或黄屏提示都只会在开发版本中显示,正式的离线包中是不会显示的。

1.11.1.1 红屏错误

        应用内的报错会以全屏红色显示在应用中(调试模式下),我们称为红屏(red box)报错。你可以使用console.error()来手动触发红屏错误。

1.11.1.2 黄屏警告

        应用内的警告会以全屏黄色显示在应用中(调试模式下),我们称为黄屏(yellow box)报错。点击警告可以查看详情或是忽略掉。 和红屏报警类似,你可以使用console.warn()来手动触发黄屏警告。 在默认情况下,开发模式中启用了黄屏警告。可以通过以下代码关闭:

console.disableYellowBox = true;

console.warn('YellowBox is disabled.');

        你也可以通过代码屏蔽指定的警告,像下面这样设置一个数组:

console.ignoredYellowBox = ['Warning: ...'];

        数组中的字符串就是要屏蔽的警告的开头的内容。(例如上面的代码会屏蔽掉所有以Warning开头的警告内容)

        红屏和黄屏在发布版(release/production)中都是自动禁用的。

1.11.2 访问控制台日志

        在运行RN应用时,可以在终端中运行如下命令来查看控制台的日志:

$react-native log-ios

$react-native log-android

        此外,你也可以在iOS模拟器的菜单中选择Debug → Open System Log...来查看。如果是Android应用,无论是运行在模拟器或是真机上,都可以通过在终端命令行里运行adb logcat *:S ReactNative:V

        ReactNativeJS:V命令来查看。

1.11.3 Chrome开发者工具

        在开发者菜单中选择"Debug JSRemotely"选项,即可以开始在Chrome中调试JavaScript代码。点击这个选项的同时会自动打开调试页面 http://localhost:8081/debugger-ui.

        在Chrome的菜单中选择Tools → Developer Tools可以打开开发者工具,也可以通过键盘快捷键来打开(Mac上是Command⌘ + Option⌥ + I,Windows上是Ctrl + Shift + I或是F12)。打开有异常时暂停(Pause On CaughtExceptions)选项,能够获得更好的开发体验。

        译注:Chrome中并不能直接看到App的用户界面,而只能提供console的输出,以及在sources项中断点调试js脚本。

        目前无法正常使用React开发插件(就是某些教程或截图上提到的Chrome开发工具上多出来的React选项),但这并不影响代码的调试。如果你需要像调试web页面那样查看RN应用的jsx结构,暂时只能使用Nuclide的"React Native Inspector"这一功能来代替。

1.11.3.1 使用Chrome开发者工具来在设备上调试#

        对于iOS真机来说,需要打开 RCTWebSocketExecutor.m文件,然后将其中的"localhost"改为你的电脑的IP地址,最后启用开发者菜单中的"Debug JS Remotely"选项。

        对于Android 5.0+设备(包括模拟器)来说,将设备通过USB连接到电脑上后,可以使用adb命令行工具来设定从设备到电脑的端口转发:

adb reverse tcp:8081 tcp:8081

        如果设备Android版本在5.0以下,则可以在开发者菜单中选择"Dev Settings- Debug server host for device",然后在其中填入电脑的”IP地址:端口“。

        如果在Chrome调试时遇到一些问题,那有可能是某些Chrome的插件引起的。试着禁用所有的插件,然后逐个启用,以确定是否某个插件影响到了调试。

1.11.3.2 使用自定义的JavaScript调试器来调试#

        如果想用其他的JavaScript调试器来代替Chrome,可以设置一个名为REACT_DEBUGGER的环境变量,其值为启动自定义调试器的命令。调试的流程依然是从开发者菜单中的"Debug JSRemotely"选项开始。

        被指定的调试器需要知道项目所在的目录(可以一次传递多个目录参数,以空格隔开)。例如,如果你设定了REACT_DEBUGGER="node /某个路径/launchDebugger.js --port 2345 --type ReactNative",那么启动调试器的命令就应该是node /某个路径/launchDebugger.js --port 2345 --type ReactNative /某个路径/你的RN项目目录。

        以这种方式执行的调试器最好是一个短进程(short-livedprocesses),同时最好也不要有超过200k的文字输出。

1.11.3.3 在Android上使用Stetho来调试

        在android/app/build.gradle文件中添加:

compile 'com.facebook.stetho:stetho:1.3.1'

compile 'com.facebook.stetho:stetho-okhttp3:1.3.1'

        在android/app/src/main/java/com/{yourAppName}/MainApplication.java文件中添加:

import com.facebook.react.modules.network.ReactCookieJarContainer;

import com.facebook.stetho.Stetho;

import okhttp3.OkHttpClient;

import com.facebook.react.modules.network.OkHttpClientProvider;

import com.facebook.stetho.okhttp3.StethoInterceptor;

import java.util.concurrent.TimeUnit;

        在android/app/src/main/java/com/{yourAppName}/MainApplication.java文件中添加:

public void onCreate() {

     super.onCreate();

     Stetho.initializeWithDefaults(this);

     OkHttpClient client = newOkHttpClient.Builder()

         .connectTimeout(0, TimeUnit.MILLISECONDS)

         .readTimeout(0, TimeUnit.MILLISECONDS)

         .writeTimeout(0, TimeUnit.MILLISECONDS)

         .cookieJar(new ReactCookieJarContainer())

         .addNetworkInterceptor(new StethoInterceptor())

         .build();

     OkHttpClientProvider.replaceOkHttpClient(client);

}

        运行react-native run-android

        打开一个新的Chrome选项卡,在地址栏中输入chrome://inspect并回车。在页面中选择'Inspect device'(标有"Powered by Stetho"字样)。

1.11.4 调试原生代码#

        在和原生代码打交道时(比如编写原生模块),可以直接从Android Studio或是Xcode中启动应用,并利用这些IDE的内置功能来调试(比如设置断点)。这一方面和开发原生应用并无二致。

        React Native中调用原生android模块Toast例子及说明

http://www.tuicool.com/articles/ayyQbyz

1.12 其他参考资源

        如果你耐心的读完并理解了本网站上的所有文档,那么你应该已经可以编写一个像样的React Native应用了。但是React Native并不全是某一家公司的作品——它汇聚了成千上万开源社区开发者的智慧结晶。如果你想深入研究ReactNative,那么建议不要错过下面这些参考资源。

1.12.1 常用的第三方库

        如果你正在使用React Native,那你应该已经对React有一定的了解了。React是基础中的基础所以我其实不太好意思提这个——但是,如果不幸你属于“但是”,那么请一定先了解下React,它也非常适合编写现代化的网站。

        开发实践中的一个常见问题就是如何管理应用的“状态(state)”。这方面目前最流行的库非Redux莫属了。不要被Redux中经常出现的类似"reducer"这样的概念术语给吓住了——它其实是个很简单的库,网上也有很多优秀的视频教程(英文)。。

        如果你在寻找具有某个特定功能的第三方库,那么可以看看别人精心整理的资源列表。这里还有个类似的中文资源列表

1.12.2 示例应用

        在React Native Playground网站上有很多示例的代码。这个网站有个很酷的特性:它直接对接了真实设备,可以实时在网页上显示运行效果。当然,对于国内用户来说,可能访问很困难。

        另外就是Facebook的F8开发大会有一个对应的app,这个app现在已经开源,其开发者还详细地撰写了相关教程。如果你想学习一个更实际更有深度的例子,那你应该看看这个。

1.12.3 开发工具

        Nuclide是Facebook内部所使用的React Native开发工具。它最大的特点是自带调试功能,并且非常好地支持flow语法规则。(译注:然而我们还是推荐webstorm或是sublimetext)。

        Ignite是一套整合了Redux以及一些常见UI组件的脚手架。它带有一个命令行可以生成app、组件或是容器。如果你喜欢它的选择搭配,那么不妨一试。

        CodePush是由微软提供的热更新服务。热更新可以使你绕过AppStore的审核机制,直接修改已经上架的应用。对于国内用户,我们也推荐由本网站提供的Pushy热更新服务,相比CodePush来说,提供了全中文的文档和技术支持,服务器部署在国内速度更快,还提供了全自动的差量更新方式,大幅节约更新流量,欢迎朋友们试用和反馈意见!

        Exponent是一套开发环境,还带有一个已上架的空应用容器。这样你可以在没有原生开发平台(Xcode或是AndroidStudio)的情况下直接编写React Native应用(当然这样你只能写js部分代码而没法写原生代码)。

        Deco是一个专为ReactNative设计的集成开发环境。它可以自动创建新项目、搜索开源组件并插入到项目中。你还可以实时地可视化地调整应用的界面。不过目前还只支持mac。

1.12.4 React Native的交流社区

        以下这些都是英文的交流区,我也就不翻译了……

        The React Native CommunityFacebook group has thousands of developers, and it's pretty active. Come there to show off your project, or ask how other people solved similar problems.

        Reactiflux is a Discord chat where a lot of React-related discussion happens, including React Native.Discord is just like Slack except it works better for open source projects with a zillion contributors. Check out the #react-native channel.

        The React Twitter account covers both React and React Native. Following that account is a pretty good way to find out what's happening in the world of React.

        There are a lot of React Native Meetups that happen around the world. Often there is React Native content in React meetups as well.

        Sometimes we have React conferences.We posted the videos from React.js Conf 2016, and we'll probably have more conferences in the future, too. Stay tuned.

1.13 iOS应用程序状态

        AppStateIOS可以告诉你应用程序是在前台还是在后台,而且状态更新时会通知你。 在处理推送通知时,AppStateIOS经常被用于判断目标和适当的行为。        

1.13.1 iOS应用程序状态

    • Active - 应用程序在前台运行

    • Background - 应用程序在后台运行。用户正在使用另一个应用程序或者在主屏幕上。

    • Inactive - 这是一种过渡状态,目前不会在ReactNative的应用程序上发生。

1.13.2 基本用法

        为了查看当前的状态,你可以检查AppStateIOS.currentState,该方法会一直保持最新状态。然而,当AppStateIOS在桥接器上检索currentState时,在启动时它将会为空。

getInitialState: function() {

  return {

    currentAppState:AppStateIOS.currentState,

  };

},

componentDidMount: function() {

  AppStateIOS.addEventListener('change', this._handleAppStateChange);

},

componentWillUnmount: function() {

  AppStateIOS.removeEventListener('change', this._handleAppStateChange);

},

_handleAppStateChange: function(currentAppState) {

  this.setState({currentAppState, });

},

render: function() {

  return (

    Current state is:{this.state.currentAppState}

  );

},

1.13.3 方法

static addEventListener(type: string, handler: Function)

       通过监听change事件类型和提供处理程序,为应用程序状态变化添加一个处理程序。

static removeEventListener(type: string, handler: Function)

        通过传递change事件类型和处理程序,删除一个处理程序。

1.14 BackAndroid

1.14.1 方法

static exitApp()

static addEventListener(eventName: BackPressEventName, handler: Function) 

static removeEventListener(eventName: BackPressEventName, handler: Function)

1.15 相机

1.15.1 方法

static saveImageWithTag(tag: string, successCallback, errorCallback)

        利用tag标签保存图像到相机相册。

@param {string} tag -可以是我们所接受的三种标签中的任意一个:

    1、url 

    2、assets-library标签

    3、存储 一个图像的内存中返回的标签


static getPhotos(params: object, callback: function, errorCallback: function)

        利用来自设备中的本地相机相册的图片识别对象来调用callback函数,通过getPhotosReturnChecker来定义 匹配类型。

@param {object} params - 见getPhotosParamChecker。

@param {function} callback - 通过Checker定义自变量的类型调用成功。

@param {function}errorCallback - 通过错误消息调用失败。

1.16 iOS震动

        震动API是在VibrationIOS.vibrate()里显示的。在iOS上,调用这个函数可以出发一秒钟的振动。振动是异步的,所以这个方法会立即返回。

        这对不支持振动的设备是没有任何影响的,例如,iOS模拟器。 目前是不支持振动模式的。

        方法

static vibrate()

1.17 定位

        你需要在 info.plist中添加NSLocationWhenInUseUsageDescription键来定位,当你用react-native init来创建一个项目时,默认情况下定位是能够使用的。

        定位遵循MDN规范:

https://developer.mozilla.org/en-US/docs/Web/API/Geolocation

1.17.1 方法

static getCurrentPosition(geo_success: Function, geo_error?:Function, geo_options?: Object)

static watchPosition(success: Function, error?: Function, options?: Object)

static clearWatch(watchID: number)

static stopObserving()

1.18 交互管理器

        交互管理器在任意交互/动画完成之后,允许安排长期的运行工作。特别是,这允许JavaScript动画可以顺利的 运行。

        应用程序可以在交互完成之后根据以下代码来安排运行任务:

InteractionManager.runAfterInteractions(() => {

  // ...long-runningsynchronous task...

});

        与其他调度方案进行比较:

    • requestAnimationFrame():代码是动画在时间上的一个视图

    • setImmediate/setTimeout():运行代码后,请注意这有可能会延迟动画

    • runAfterInteractions():运行代码后,没有延迟的动态动画

        触发处理系统将一个或者多个动态触发看成是一个“交互”,并且将推迟runAfterInteractions()回调直到所 有的触发都已经结束或者被取消了。

        交互管理器还允许应用程序通过创建一个“处理”动画的开端来注册动画,结束之后进行清除:

var handle = InteractionManager.createInteractionHandle();

// run animation... (`runAfterInteractions` tasks arequeued)

// later, on animation completion:


InteractionManager.clearInteractionHandle(handle);

// queued tasks run if all handles were cleared

1.18.1 方法

static runAfterInteractions(callback: Function)

        在所有交互都完成之后安排一个函数来运行。

static createInteractionHandle()

        通知管理器已经启动了一个交互。

static clearInteractionHandle(handle: number)

        通知管理器一个交互动作已经完成了。

1.19 动画布局

1.19.1 方法

static configureNext(config: Config, onAnimationDidEnd?:Function, onError?: Function)

static create(duration: number, type, creationProp)

1.20 网络信息

        网络信息公开在线或者离线信息

1.20.1 reachabilityIOS

        异步确定设备是否处于在线状态并且在蜂窝网络。

    • None - 设备处于离线状态

    • WiFi - 设备处于在线状态,并且通过WiFi或者是iOS模拟器连接

    • Cell - 设备通过网络连接,3G,WiMax,或者LTE进行连接

    • Unknown - 错误情况,并且网络状态未知

NetInfo.reachabilityIOS.fetch().done((reach) => {

  console.log('Initial: ' + reach);

});

function handleFirstReachabilityChange(reach) {

  console.log('Firstchange: ' + reach);

 NetInfo.reachabilityIOS.removeEventListener('change', handleFirstReachabilityChange);

}

    NetInfo.reachabilityIOS.addEventListener('change', handleFirstReachabilityChange

);

1.20.2 连接状态

        在所有的平台上都可用。异步获取一个布尔值来确定网络连接。

NetInfo.isConnected.fetch().done((isConnected) => {

  console.log('First, is ' + (isConnected ? 'online' : 'offline'));

});

function handleFirstConnectivityChange(isConnected) {

   console.log('Then, is ' + (isConnected ? 'online' : 'offline'));

   NetInfo.isConnected.removeEventListener(

        'change',

        handleFirstConnectivityChange

   );

   NetInfo.isConnected.addEventListener(

       'change',

      handleFirstConnectivityChange

   );

 }

1.21 像素比率

        PixelRatio类为像素密度设备提供了访问权。 这里有一些使用PixelRatio的用例:

    显示一条和设备许可一样细的线

        宽度1实际上相当于iPhone4+的厚度,我们可以使用设定宽度为1 / PixelRatio.get()的函数来实现。这是 一项独立于像素密度的应用在所有设备上的技术。

style={{ borderWidth: 1 / PixelRatio.get() }}

    获取一个正确大小的图像

        如果你使用的是一台像素密度比较高的设备上,那你应该得到一个更高分辨率的图像。一个好的经验法则是在pi xel ratio上显示多种图像的尺寸。

var image = getImage({

  width: 200 *PixelRatio.get(),

  height: 100 *PixelRatio.get()

});

<Image source={image} style={{width: 200, height: 100}} />

1.21.1 方法

static get()返回设备的像素密度。一些例子:

    • PixelRatio.get() === 2

    • iPhone 4, 4S

    • iPhone 5, 5c, 5s

    • iPhone 6

    • PixelRatio.get() === 3

    • iPhone 6 plus


产品描述

[Edit onGitHubhref="https://github.com/facebook/react-native/blob/master/docs/PixelRatio.md")

1.21.2 像素网格拍摄

        在iOS里,你可以为元素指定有任意精度的位置和尺寸,例如29.674825。但是,最终的物理显示就只有一个固 定的像素值,例如在iPhone4上是640960,或者在iPhone6上是7501334。iOS试图通过将一个原始的像素扩 展成多个值得方法,看似是尽可能忠实于用户的体验价值,实际上是欺骗了众人的眼睛。这项技术的缺点是使得 生成的元素看起来很模糊。

        实际上,我们发现开发人员并不需要这项功能,但是为了避免生成模糊的像素,他们不得不对它进行手动舍入操 作。在React Native里,我们都是自动对这些元素进行舍入。

        在进行舍入时,我们必须相当的小心。你永远不希望在同一时间使用正常值和四舍五入的值,那就好像你正在不断的积累舍入误差。甚至一个舍入误差会造成致命性的错误,因为一个像素边界可能会消失或者变成两倍那么大。

        在React Native里,在JS和布局引擎里的一切值都是以一个任意精度的数来进行工作的。这只会发生在当在为 主线程里我们进行舍入的原生元素设定任意位置和尺寸的时候。同时,舍入操作是针对根而不是父母完成的,这又一次避免了累积舍入误差。

1.22 iOS推送通知

        为你的应用程序处理推送通知,包括权限的处理和图标标记数量。

1.22.1 方法

static setApplicationIconBadgeNumber(number: number)

        在主屏幕上为应用程序的图标设置标记数量

static getApplicationIconBadgeNumber(callback: Function)

        在主屏幕上为应用程序的图标获取当前的标记数量

static addEventListener(type: string, handler: Function)

        当应用程序在前台或者后台运行的时候,为了远程通知链接一个监听器。 处理程序将会以一个PushNotificationIOS的实例的形式被调用

static requestPermissions()

        从iOS上请求所有的通知权限,提示用户对话框

static checkPermissions(callback: Function)

        查看当前正被启用的推送权限。Callback函数将被一个permission的对象调用:

    • alert :boolean

    • badge :boolean

    • sound :boolean

static removeEventListener(type: string, handler: Function)

        删除事件监听器。为了防止内存泄露,该操作在componentWillUnmount里完成。

static popInitialNotification()

        如果应用程序从一个通知被冷发射,那么一个原始通知将变成可用状态。popInitialNotification的第一个调用者将获取最初的通知对象,或者为null。后续的调用将返回null。

constructor(nativeNotif)

        你自己可能永远都不需要instansiatePushNotificationIOS。你只需要监听notification事件并且调pInitialNotification就足够了。

getMessage()

        getAlert的一个别名,该函数是为了获取通知的主要消息字符串

getSound()

        从aps对象中获取声音字符串

getAlert()

        从aps对象中获取通知的主要消息字符串

getBadgeCount()

        从aps对象中获取标记数量

getData()

        在通知上获取数据对象

1.23 iOS状态栏

1.23.1 方法

static setStyle(style: number, animated?: boolean)

static setHidden(hidden: boolean, animation: number)

1.24 样式表

        一个样式表是一个类似于CSS样式表的抽象体 创建一个新的样式表:

var styles = StyleSheet.create({

  container: {

    borderRadius: 4,

    borderWidth: 0.5,

    borderColor:'#d6d7da',

}, title: {

    fontSize: 19,

    fontWeight: 'bold',

  },

  activeTitle: {

    color: 'red',

}, });

使用样式表:

<View style={styles.container}>

    <Text style={[styles.title, this.props.isActive && styles.activeTitle]} />

</View>

        代码质量:

    • 通过移动样式渲染功能,你可以使你的代码理解起来更容易。

    • 对样式进行命名,对在渲染功能的低水平组件中添加意义是一个很好的方式。

        性能:

    • 在样式对象中使用一个样式表可以使得通过ID对它进行参考成为可能,而不是每一次都创建一个新的样式对象。

    • 它还允许通过桥梁对样式进行一次发送。所有后续的使用都是通过id(尚未实施)。

1.24.1 方法

static create(obj: {[key: string]: any})

1.25 计时器

        计时器是一个应用程序的重要的一个组成部分,ReactNative实现了Browser timers。

1.25.1 计时器

    • setTimeout, clearTimeout

    • setInterval, clearInterval

    • setImmediate, clearImmediate

    • requestAnimationFrame, cancelAnimationFrame

    requestAnimationFrame(fn)相当于setTimeout(fn, 0),他们是在刷新屏幕之后被正确触发。

    setImmediate是在向本地发送批处理相应之前,当前JavaScript执行块结束时执行的。注意,如果你在一个回调函数setImmediate之内调用setImmediate,它将立即被执行,而且不会返回到本地之间。 这个Promise的实现是将setImmediate作为异步性的开端。

1.25.2 交互管理器

        良好的原生应用可以用起来感觉很顺利的一个原因是在交互和动画方面避免了复杂的操作。在React Native,目前我们有一个限制,只有一个JS执行线程,但是你可以使用InteractionManager来确保在任一交互或者动画完 成之后,长期的运行工作的开始是被规划好的。

        在下面的交互完成之后,应用程序可以安排任务来运行:

InteractionManager.runAfterInteractions(() => {

    // ...long-runningsynchronous task...

});

        与其他调度方案相比:

    • requestAnimationFrame():代码是在时间上的一个动画视图

    • setImmediate/setTimeout/setInterval():运行代码之后,请注意这可能会延迟动画

    • runAfterInteractions():运行代码之后,没有延迟的动态动画

        触发处理系统将一个或多个触发看作是一个“交互”,并且将runAfterInteractions()延迟回调,直到所有的触 发都已结束或者被取消。

        交互管理器还允许应用程序通过对动画的开始创建一个交互“处理”来注册动画,并且完成之后进行清理:

     var handle = InteractionManager.createInteractionHandle();

     // run animation... (`runAfterInteractions` tasks are queued)

     // later, on animation completion:

    InteractionManager.clearInteractionHandle(handle);

     // queued tasks run if all handles were cleared

1.25.3 TimerMixin

        我们发现在React Native上的应用程序出现致命性问题的主要原因是由于一个组件被卸载后计时器就会被触发。为了解决这个反复出现的问题,我们引入了TimerMixin。如果你有TimerMixin,那么你可以用this.set Timeout(fn, 500) (只是加上 this. )来替换setTimeout(fn, 500)函数的调用,并且当组件被卸载时,一切 都会被清理干净。

var TimerMixin = require('react-timer-mixin');

var Component = React.createClass({

 mixins : [TimerMixin],

 componentDidMount:function() {

     this.setTimeout(

        () => {console.log('I do not leak!'); },

         500

     ); }

 });

        我们强烈建议不用只单独使用Timers,而是一直使用mixin,这样将会为你节省很多很难追踪的bugs。

2 参考链接

React Native之调用安卓原生控件

http://blog.csdn.net/jj120522/article/details/51968278


React-Native之Android:原生界面与React界面的相互调用

http://www.jianshu.com/p/f1b265e80317


react-native调用原生模块详解

http://blog.csdn.net/woaini705/article/details/50899946


使用React-Native Code push热更新 增量更新 动态修复bug移动开发

http://www.jianshu.com/p/ec8d64681e53


React Native官方文档中文版

http://wiki.jikexueyuan.com/project/react-native/native-ui-components.html


React中文版

http://wiki.jikexueyuan.com/project/react/


React Native中文网

http://reactnative.cn


React Native中调用原生android模块Toast例子及说明

http://www.tuicool.com/articles/ayyQbyz


React Native教程第一部分:Hello, React

http://www.tuicool.com/articles/MJZ3ym

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容