ReactNative 学习记录(一)

RN 的环境搭建和基础操作参照官方文档:查看

学习做一个 Movie Fetcher


俗话说前人栽树,后人乘凉.我们在没有什么 RN 基础的时候,跟着前辈已有的项目教程学着写代码显然是个不错的选择.

参考项目地址:查看 参考源码:查看

我使用RN 版本的是目前的最新版0.39

(一)第一部分:初学

1. 模拟数据(Mocking data)

在我们写代码去获取加载真实的数据之前,我们先来模拟一下数据。一般我们会声明一些常量在JS文件的头部,仅仅在imports语句下面。当然了,你可以添加在其他一些地方,只要你喜欢即可。下面是index.ios.js以及index.android.js需要添加的代码:

var MOCKED_MOVIES_DATA = [
  {title: 'Title', year: '2015', posters: {thumbnail: 'http://i.imgur.com/UePbdph.jpg'}},
];

2.渲染一条电影数据(Redner a movie)

接下来我们渲染显示电影的标题,年份以及电影的缩略图。缩略图是React Native中的Image组件进行显示,然后我们需要导入Image组件

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

我们修改render()方法来进行渲染该条电影数据:

render() {
    var movie = MOCKED_MOVIES_DATA[0];
    return (
      <View style={styles.container}>
        <Text>{movie.title}</Text>
        <Text>{movie.year}</Text>
        <Image source={{uri: movie.posters.thumbnail}} />
      </View>
    );
  }

}
然后我们打开开发者菜单/点击Reload JS,你可以看到"Title"和"2015"这两个数据,当然你会注意到Image没有任何渲染。这是因为你的Image组件没有指定宽和高。这个你可以通过定义Style实现,接下来我们来写一个样式。

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  thumbnail: {
    width: 53,
    height: 81,
  },
});

然后再添加样式:

       <Image
         source={{uri: movie.posters.thumbnail}}
         style={styles.thumbnail}
       />

渲染之后可能图片不会出现
可以用官方样例测试一下

<Image source={{uri: 'https://facebook.github.io/react/img/logo_og.png'}}
       style={{width: 400, height: 400}} />

如果官方图片可以显示 说明可能是墙的问题
建议 把图片存到本地 通过其他方法调用


index.ios.js
/**
 * Sample React Native App
 */

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

var MOCKED_MOVIES_DATA = [
  {title: 'Title', year: '2015'},
];

export default class MovieFetcher extends Component {
  render() {
    var movie = MOCKED_MOVIES_DATA[0];
    return (
      <View style={styles.container}>
        <Text style={styles.title}>{movie.title}</Text>
        <Text style={styles.year}>{movie.year}</Text>
        <Image source={require('./img/UePbdph.jpg')} 
               style={styles.thumbnail} />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  thumbnail: {
    width: 53,
    height: 81,
  },

});

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


这是目前的效果

我们来分析一下代码:

1.样式
const styles = StyleSheet.create({})

这个样式部分是与原生 js 最接近的 也很好理解
有种 html 里面加了 className 然后在 css 里面加属性的感觉

你只需要通过JavaScript定义应用的样式即可。
所有核心的组件都有style的属性。
该样式的名称和属性值几乎和Web端的CSS样式差不多,不过需要修改成驼峰命名法,例如:这边使用backgroudColor代替background-color。

下面两段代码的作用是一致的:

...
<Image source={require('./img/UePbdph.jpg')} 
               style={styles.thumbnail} />
...
 
const styles = StyleSheet.create({
  thumbnail: {
    width: 53,
    height: 81,
  },
});
<Image source={require('./img/UePbdph.jpg')} 
               style={ width: 53, height: 81} />

而前者 建立样式表的方式 便于管理和复用

2.import
import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  Image,
} from 'react-native';

这个部分主要添加必要的模块

3.定义
  • 常量
var MOCKED_MOVIES_DATA = [
  {title: 'Title', year: '2015'},
];

这个也没什么好说的
定义了一个数组对象 里面有一个 json json 作为一个轻量的存储方式很适合用来存一些数据

export default class MovieFetcher extends Component {}

这里定义了一个叫做MovieFetcher的类
export 可以将该类导出 以便于其他 js 文件导入使用
为方便理解,可以把类的定义看做 函数定义 ,当然这两者不等同
default 一个文件中只能用一次

下面这个类也是合法形式

class Project extends Component{}
4.render()

这个是一个渲染器
原生 js 运行时由浏览器进行渲染 RN 里面通过虚拟 Dom进行渲染

render() 里面是什么呢 是核心组件Core Components
我们再来看一眼上面的内容

export default class MovieFetcher extends Component {}
  • 这里就有个 component
    核心组件介绍: 查看
    像一些 Text 组件. Image 组件 TextInput 组件等等

要注意的是: 每次使用组件都要在文件头部看一下有没有引入相应的模块



如果这里没有引入 Image 模块
我们刚刚的代码就要出错啦

然后改一改布局



把内容显示变成这样

1.图片和文字作为整体垂直居中
2.文字部分占据横向空余空间

(二)第二部分:添加初始化的过程

平时我们在看到内容加载的时候 会有进度条或者转动的圆圈显示在界面上 接下里就要做这个效果

按照教程敲完代码

可以实现 图片还是老问题

我们先不管图片 来看一下代码

/**
 * Sample React Native App
 */

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


var REQUEST_URL = 'https://raw.githubusercontent.com/facebook/react-native/master/docs/MoviesExample.json';

class MovieFetcher extends Component {
    constructor(props) {
      super(props);
      this.state = {
        movies: null,
      };
     }
    componentDidMount() {
      this.fetchData();
    }
    fetchData() {
      fetch(REQUEST_URL)
        .then((response) => response.json())
        .then((responseData) => {
          this.setState({
            movies: responseData.movies,
          });
        })
      .done();
    }

    render() {
        if (!this.state.movies) {
          return this.renderLoadingView();
        }
     
        var movie = this.state.movies[0];
        return this.renderMovie(movie);
      }
     
      renderLoadingView() {
        return (
          <View style={styles.container}>
            <Text>
              Loading movies...
            </Text>
          </View>
        );
      }
     
      renderMovie(movie) {
        return (
          <View style={styles.container}>
            <Image
              source={{uri: movie.posters.thumbnail}}
              style={styles.thumbnail}
            />
            <View style={styles.rightContainer}>
              <Text style={styles.title}>{movie.title}</Text>
              <Text style={styles.year}>{movie.year}</Text>
            </View>
          </View>
        );
      }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  rightContainer: {
    flex: 1,
  },
  title: {
    fontSize: 30,
    marginBottom: 8,
    textAlign: 'center',
  },
  year: {
    fontSize: 18,
    textAlign: 'center',
  },
  thumbnail: {
    width: 53,
    height: 81,
  },

});

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

样式就不说了
来说一说 Props(属性)与State(状态)

  • 属性 props

上面的 image 组件

            <Image
              source={{uri: movie.posters.thumbnail}}
              style={styles.thumbnail}
            />

这里的 source就是一个属性 用来选择图片的来源
除了自定义常量可以给属性赋值外
自定义的组件也是可以使用props的

...
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>
    );
  }
我们来改变index.ios.js 跑一下加深一下对 prop 的理解
/**
 * Sample React Native App
 */

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

class Greeting extends Component {
  render() {
    return (
      <Text>Hello {this.props.name}!</Text>
    );
  }
}
 
class MovieFetcher extends Component {
  render() {
    return (
      <View style={{alignItems: 'center',marginTop: 100,}}>
        <Greeting name='Rexxar' />
        <Greeting name='Jaina' />
        <Greeting name='Valeera' />
      </View>
    );
  }
}

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

看一下效果


猜测 this.props 里面存了所有的属性值 调用 name 显示 name 值

  • state

不知道大家有没有了解过状态机



状态机 就是一个状态执行完就等待触发然后跳到下一个状态执行的一种机制.

props是在父组件中进行设置,只要设置完成那么该在组件的声明周期中就定死了,不会发生改变。所以针对数据变化修改的情况,我们需要使用state属性。

一般情况下,我们需要在constructor方法中进行初始化state,然后在你想要修改更新的时候调用setState方法即可。

state 的改变充当了触发条件
render()充当了执行过程
render() 里面的内容就是需要执行的内容

例如:我们现在需要制作一段不断进行闪动的文字效果,文字内容当组件创建好的时候就已经指定了。文字内容通过prop展现。但是通过时间控制文字闪动的状态通过state实现。一起来看一下如下的代码:

改变 index.ios.js
/**
 * Sample React Native App
 */

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

 
class Blink extends Component {
  constructor(props) {
    super(props);
    this.state = {showText: true};
 
    // Toggle the state every second
    setInterval(() => {
      this.setState({ showText: !this.state.showText });
    }, 1000);
  }
 
  render() {
    let display = this.state.showText ? this.props.text : ' ';
    return (
      <Text>{display}</Text>
    );
  }
}
 
class MovieFetcher extends Component {
  render() {
    return (
      <View style={{flex: 1,alignItems: 'center',justifyContent: 'center',}}>
        <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('MovieFetcher', () => MovieFetcher);

我们来看一下代码部分
为了使用 <Blink text='I love to blink' />
自定义了一个Blink类

class Blink extends Component {}

首先初始化 state

  • props来自于父组件或者自身getDefaultProps
    这里就传入了 <Blink text='I love to blink' />里面的 text 属性
  • 接着初始化了一个state的showText为true的state对象
constructor(props) {
    super(props);
    this.state = {showText: true};
    ...
}
  • 接下来的定时器的作用是每隔一秒钟使得 state 对象里的 showText 布尔值取反 设置 state 需要用到 setState 方法
    ...
    setInterval(() => {
      this.setState({ showText: !this.state.showText });
    }, 1000);
    ...
  • 渲染虚拟 Dom: 当showText布尔值为 true 的时候显示 反之不显示
  render() {
    let display = this.state.showText ? this.props.text : ' ';
    return (
      <Text>{display}</Text>
    );
  }

看到这里大家对 prop 和 state 都有一定了解了

  • 我们再回过头去看之前的代码
(一) 首先定义一个全局常量 一个 json 对象
var REQUEST_URL = 'https://raw.githubusercontent.com/facebook/react-native/master/docs/MoviesExample.json';
(二)然后定义MovieFetcher类
class MovieFetcher extends Component {}
(三) 初始化 state: this.state.movie 设为 null
    ...
    constructor(props) {
      super(props);
      this.state = {
        movies: null,
      };
     }
    ...
(四) 当调用componentDidMount的时候执行this.fetchData()方法

componentDidMount:在render渲染之后,React会根据Virtual DOM来生成真实DOM,生成完毕后会调用该函数。在浏览器端(React),我们可以通过this.getDOMNode()来拿到相应的DOM节点。然而我们在RN中并用不到,在RN中主要在该函数中执行网络请求,定时器开启等相关操作
componentDidMount方法方法只会在组件完成加载的时候调用一次。

  ...
  componentDidMount() {
      this.fetchData();
  }
  ...
(五) this.fetchData()

接下来我们添加一个fetchData方法来加载处理数据。我们需要在数据加载成功之后调用this.setState({moves:data}),要知道该setState方法会触发控件重新渲染,同时也会注意到this.state.moves不会一直未null。该方法最后我们调用done()方法,我们需要确保最后调用done()方法,这样有任何异常我们可以拦截到并且处理。

    ...
    fetchData() {
       //fetch ->get方法,只填写url参数
      fetch(REQUEST_URL)
       //上面一行会返回响应对象,即response
        .then((response) => response.json())
       //response.json()将返回一个json类型对象
        .then((responseData) => {
          this.setState({
            movies: responseData.movies,
          });
        })
      //注意我们在Promise调用链的最后调用了done() —— 这样可以抛出异常而不是简单忽略。
      .done();
    }
    ...
(六) render()

当moves数据为空的时候显示一个正在加载的视图
不为空的时候加载 json 里面的数据

render() {
    if (!this.state.movies) {
      return this.renderLoadingView();
    }
    var movie = this.state.movies[0];
    return this.renderMovie(movie);
  }
  renderLoadingView() {
    return (
      <View style={styles.container}>
        <Text>
          Loading movies...
        </Text>
      </View>
    );
  }
  renderMovie(movie) {
    return (
      <View style={styles.container}>
        <Image
          source={{uri: movie.posters.thumbnail}}
          style={styles.thumbnail}
        />
        <View style={styles.rightContainer}>
          <Text style={styles.title}>{movie.title}</Text>
          <Text style={styles.year}>{movie.year}</Text>
        </View>
      </View>
    );
  }
(七)试着用豆瓣电影的 API 来改写上面的代码
//地址
"https://api.douban.com/v2/movie/subject/1764796"

改动:

效果:

(三)第三部分:添加 ListView列表组件

先来看一下 ListView的官方例子

改变 index.ios.js
import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  Image,
  ListView,
} from 'react-native';
var MovieFetcher = React.createClass({
    getInitialState: function() {
      var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
        return {
          dataSource: ds.cloneWithRows(['row 1', 'row 2','row 3','row 4','row 5','row 6','row 7','row 8']),
        };
    },
    render: function() {
      return (
        <ListView
          dataSource={this.state.dataSource}
          renderRow={(rowData) => <Text>{rowData}</Text>}
          style={{marginTop: 20}}
        />
      );
    }
});
AppRegistry.registerComponent('MovieFetcher', () => MovieFetcher);

效果:


  • getInitialState()

它的 getInitialState方法用于定义初始状态,也就是一个对象,这个对象可以通过 this.state属性读取。当用户点击组件,导致状态变化,this.setState方法就修改状态值,每次修改以后,自动调用 this.render方法,再次渲染组件。

  • rowHasChanged

rowHasChanged(prevRowData, nextRowData):指定我们更新row的策略,一般来说都是prevRowData和nextRowData不相等时更新row
rowHasChanged函数可以告诉ListView它是否需要重绘一行数据。

  • cloneWithRows(dataBlob, rowIdentities)

该方法接收两个参数,dataBlob是原始数据源。在没有section,传入一个纯数组的时候使用此方法。rowIdentities为可选类型,为数据源的每一项指明一个id。默认的id为字符串'0','1','2'...dataBlob.count。

  • dataSource ListViewDataSource 设置ListView的数据源

dataSource :该属性,用于为ListView指定当前的数据源

  • renderRow function 方法

(rowData,sectionID,rowID,highlightRow)=>renderable 该方法有四个参数,其中分别为数据源中一条数据,分组的ID,行的ID,以及标记是否是高亮选中的状态信息。
renderRow :该属性用来标示ListView中每一行需要显示的样子。参数表示当前行需要显示的数据

开始添加 ListView
  • import ListView组件
import {
  AppRegistry,
  Image,
  ListView,
  StyleSheet,
  Text,
  View,
} from 'react-native';

然后我们修改render方法,使用ListView渲染加载电影数据而不是单条记录

render() {
  if (!this.state.loaded) {
    return this.renderLoadingView();
  }
  return (
    <ListView
      dataSource={this.state.dataSource}
      //调用 renderMovie方法
      renderRow={this.renderMovie}
      style={styles.listView}
    />
  );
}

dataSource是一个Listview的接口用来确定在数据更新过程中那些行发生了变化。
看上面的代码你也会注意到通过this.state来访问dataSource,那么接下来就需要在constructor()构造方法中创建一个空的dataSource。
现在通过dataSource来进行存储数据,那么我们需要定义一个状态:this.state.loaded来确保获取加载数据不出现重复请求存储。

constructor(props) {
   super(props);
   this.state = {
     dataSource: new ListView.DataSource({
       rowHasChanged: (row1, row2) => row1 !== row2,
     }),
     loaded: false,
   };
 }

最后我们给ListView控件添加一个Style样式风格

listView: {
   paddingTop: 20,
   backgroundColor: '#F5FCFF',
 },

万里长征终于跨出了第一步~ 撒花~

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

推荐阅读更多精彩内容