React Native框架内部提供了很多的内置组件,如图3-1所示。包括基本组件,如View、Text等,用于一些功能布局的Button、Picker等,iOS平台与Android平台的特定组件、API等。同时也提供了接口便于与原生平台进行交互。后续的章节我们会介绍与原生平台的混合实战开发。
在介绍React框架的章节,我们理解了如何将代码渲染至虚拟DOM并更新到真实DOM的过程。在React Native框架中,渲染到iOS平台与Android平台的过程如图3-2所示。
图3-1 React Native框架构成
图3-2 React Native渲染
在React框架中,JSX源码通过React框架最终渲染到了浏览器的真实DOM中,而在React Native框架中,JSX源码通过React Native框架编译后,通过对应平台的Bridge实现了与原生框架的通信。如果我们在程序中调用了React Native提供的API,那么React Native框架就通过Bridge调用原生框架中的方法。
因为React Native的底层为React框架,所以,如果是UI层的变更,那么就映射为虚拟DOM后调用diff算法计算出变动后的JSON映射文件,最终由Native层将此JSON文件映射渲染到原生App的页面元素上,实现了在项目中只需控制state以及props的变更来引起iOS与Android平台的UI变更。
编写的React Native代码最终会被打包生成一个main.bundle.js文件供App加载,此文件可以存储在App设备本地,也可以存储于服务器上,以供App下载更新,后续章节讲解的热更新就会涉及main.bundle.js位置的设置问题。
React Native在与原生框架通信中,如图3-3所示,采用了JavaScriptCore作为JS VM,中间通过JSON文件与Bridge进行通信。若使用Chrome浏览器进行调试,那么所有的JavaScript代码都将运行在Chrome的V8引擎中,与原生代码通过WebSocket进行通信。
图3-3 React Native与原生平台的通信
React Native开发最基本的元素就是组件,React Native与React一样,也会涉及组件之间的通信,便于数据在组件之间传递,下面列出了几种常用的组件间通信方式。
如同之前介绍React组件间参数传递一样,在React Native中,可以通过props的形式实现父组件向子组件传递值。
在下例中,父组件通过调用子组件并赋值子组件的name为React,子组件通过this.props.name获取父组件传递过来的name的字符串值React。
完整代码在本书配套源码的03-04文件夹。
1. /** 2. * 章节: 03-04 3. * 父子组件通信,在父组件中调用子组件 4. * FilePath: /03-04/parent-2-child.js 5. * @Parry 6. */ 7. 8. <ChildComponent name='React'/> 9. 10. /** 11. * 章节: 03-04 12. * 子组件实现,通过 props 获取父组件传递的值 13. * FilePath: /03-04/parent-2-child.js 14. * @Parry 15. */ 16. 17. class ChildComponent extends Component { 18. render() { 19. return ( 20. <Text>Hello {this.props.name}!</Text> 21. ); 22. } 23. }
在开发过程中,还会有子组件向父组件通信传递值的需求,比如当子组件的某个值变更后,需要通知到父组件做相应的变更与响应,那么就需要子父组件之间的通信。
例如,在父组件的定义中,在调用子组件时,同样向子组件传递了一个参数,不过这个参数是一个函数,此函数用于接收后续子组件向父组件传递过来的数据,与之前父组件向子组件传递数据不太一样。
完整代码在本书配套源码的03-04文件夹。
1. /** 2. * 章节: 03-04 3. * 子父组件通信,父组件的实现 4. * FilePath: /03-04/child-2-parent.js 5. * @Parry 6. */ 7. import React, {Component} from 'react'; 8. import ChildComponent from './ChildComponent' 9. 10. class App extends Component { 11. constructor(props) { 12. super(props) 13. this.state = { 14. name: 'React' 15. } 16. } 17. 18. //传递到子组件的参数,不过参数是一个函数。 19. handleChangeName(nickName) { 20. this.setState({name: nickName}) 21. } 22. 23. render() { 24. return ( 25. <div> 26. <p>父组件的 name:{this.state.name}</p> 27. <ChildComponent 28. onChange={(val) => { 29. this.handleChangeName(val) 30. }}/> 31. </div> 32. ); 33. } 34. } 35. 36. export default App;
下面为子组件的定义,子组件在页面中定义了一个按钮,点击此按钮调用自身的一个函数handleChange,修改了自身state中的值name为nickName定义的值Parry,那么此子组件的页面上的字符串将由之前的Hello React!变为Hello Parry!,同时使用了this.props.changeName,也就是父组件调用时传递过来的函数,向父组件传递了nickName的值Parry。
父组件在接收到子组件的调用后,调用了父组件自身的函数handleChange Name修改了自身的state中的name的值为Parry,也就是子组件传递过来的Parry,所以,父组件的页面上的值也同时由之前的React变更成了Parry。代码如下:
1. /** 2. * 章节: 03-04 3. * 子父组件通信,子组件的实现 4. * FilePath: /03-04/child-2-parent.js 5. * @Parry 6. */ 7. 8. import React, {Component} from 'react' 9. 10. export default class ChildComponent extends Component { 11. constructor(props) { 12. super(props) 13. 14. this.state = { 15. name: 'React' 16. } 17. } 18. 19. handleChange() { 20. const nickName = 'Parry'; 21. this.setState({name: nickName}) 22. //调用父组件传递过来的函数参数,传递值到父组件去。 23. this 24. .props 25. .changeName(nickName) 26. } 27. 28. render() { 29. const {name} = this.state; 30. return ( 31. <div> 32. <p>Hello {name}!</p> 33. <Button 34. onPress={this 35. .handleChange 36. .bind(this)} 37. title="修改一下 name 为 Parry"/> 38. </div> 39. ) 40. } 41. }
如果组件之间的父子层级非常多,需要进行组件之间的传递,这时候当然可以通过上面介绍的方法逐级传递,但这样的传递方法不是一个太好的方法。
因为组件之间通信冗长,嵌套逻辑太深,会导致用户体验不好,可以想象一下用户从最底层一层层操作返回到最顶层时的体验。
可以使用如context对象或global等方式进行多级组件间的通信,但是不推荐这种方式。最好不要让组件之间的层级关系太深。
前面提到的都是有层级关系的组件间的通信方式,如果组件间没有层级关系的话,则可以通过如AsyncStorage或JSON文件等方式进行通信。
当然,还可以使用EventEmitter/EventTarget/EventDispatcher继承或实现接口的方式、Signals模式或Publish/Subscribe的广播形式,都可以达到无直接关系组件间的通信。
这些组件间的通信方式使得组件之间可以传递数据,后续的实战章节会有详细的代码实现,这里主要进行了理论的介绍。掌握这部分知识后才可以将App开发中的基本单位(也就是组件)串联起来。