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 (
{movie.title}
{movie.year}
);
}
}
然后我们打开开发者菜单/点击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,
},
});
然后再添加样式:
渲染之后可能图片不会出现
可以用官方样例测试一下
如果官方图片可以显示 说明可能是墙的问题
建议 把图片存到本地 通过其他方法调用
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 (
{movie.title}
{movie.year}
);
}
}
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。
下面两段代码的作用是一致的:
...
...
const styles = StyleSheet.create({
thumbnail: {
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 (
Loading movies...
);
}
renderMovie(movie) {
return (
{movie.title}
{movie.year}
);
}
}
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 组件
这里的 source就是一个属性 用来选择图片的来源
除了自定义常量可以给属性赋值外
自定义的组件也是可以使用props的
...
class Greeting extends Component {
render() {
return (
Hello {this.props.name}!
);
}
}
class LotsOfGreetings extends Component {
render() {
return (
);
}
我们来改变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 (
Hello {this.props.name}!
);
}
}
class MovieFetcher extends Component {
render() {
return (
);
}
}
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 (
{display}
);
}
}
class MovieFetcher extends Component {
render() {
return (
);
}
}
AppRegistry.registerComponent('MovieFetcher', () => MovieFetcher);
我们来看一下代码部分
为了使用
自定义了一个Blink类
class Blink extends Component {}
首先初始化 state
- props来自于父组件或者自身getDefaultProps
这里就传入了 里面的 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 (
{display}
);
}
看到这里大家对 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 (
Loading movies...
);
}
renderMovie(movie) {
return (
{movie.title}
{movie.year}
);
}
(七)试着用豆瓣电影的 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 (
{rowData} }
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 (
);
}
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',
},
万里长征终于跨出了第一步~ 撒花~