React简介:
前面只是简单介绍移动APP开发,后面还会继续深入介绍移动app开发;其中想要用ReactNative开发出更出色的应用,那么就得学好React,下面将介绍React:
React 是一个由 Facebook 开发用于构建用户界面的渐进式 JavaScript 库,其特点:声明式设计、高效、灵活、JSX、组件化、单向数据流。
React也是组件化的,与vue不同的是:React直接使用JS代码编写组件(结构、样式、逻辑混合在js代码中)。
React是前端三大框架中诞生最早的框架,社区庞大,技术团队实力雄厚。
虚拟DOM:
操作原生DOM是一件非常耗性能的事,操作虚拟DOM就不会那么耗性能了,操作虚拟DOM时采用Different算法,只更新变化的虚拟DOM部分;虚拟DOM指通过程序员手动模拟出来类似原生DOM的对象,如下面模拟一个带有链接的p元素:
var p = {//定义一个对象tagName:'p',//标签名为:pattrs:{//定义属性:class:'font16'},children:[//定义内容,相当于innerText'跳转到:',{//在父标签的children中再定义以对象,其方法和定义p一样:tagName:'a',//标签名为:aattrs:{//属性:href:'https://www.baidu.com',},children:[//定义内容:'百度']}]}
Diff算法:
Diff算法用于对比新旧虚拟DOM的算法,其中有三部分:tree diff、component diff、element diff,其区别:
tree diff :新旧DOM树逐层对比的方式,对比所有层节点来找到被更新的节点后修改旧DOM。
component diff:组件之间的对比,当对比组件的时候,如果两个组件的类型相同,则这个组件暂时不需要被更新,如果组件的类型不同,则立即移除旧组件,新建一个组件,替换到被移除的位置。
element diff:组件中每个元素之间的对比。
使用虚拟DOM创建React项目(导入资源型):
使用React开发项目时,必须安装两个包:react、react-dom;react是用来创建组件、组件生命周期等。react-dom用来操作DOM。创建react项目步骤:
//1.新建一个项目文件夹,并在文件夹打开终端键入:npm init -y 初始化一个package.json文件//2.终端输入:cnpm install react react-dom --save 安装react和react-dom到运行里,ReactNative开发中不建议使用cnpm装包//3.新建src文件夹并新建main.js文件并引入:react和react-dom:开始编写react中js主文件(并新建index.html文件,文件中留渲染的div)import React from 'react';import ReactDOM from 'react-dom';//4.在main.js文件中使用React提供的API操作元素://4-1使用React.createElement()创建一个虚拟DOM,接收至少三个参数:参数1:字符串(标签类型),参数2:对象(标签属性),参数3开始:当前元素子节点,可放多个虚拟dom,如:var mydiv = React.createElement('div',{class:'mydiv',id:'box'},'这是一个div元素');//<div class='mydiv' id='box'>这是一个div元素</div>var spanp = React.createElement('span',{class:'spans'},'被span标签包裹的文本');//<span class='spans'>被span标签包裹的文本</span>var textp = React.createElement('p',{class:'tp'},'p标签文本中',spanp);//<p class='tp'>p标签文本中<span class='spans'>被span标签包裹的文本</span></p>// 4-2使用ReactDOM.render()将虚拟DOM渲染到页面中,参数1:渲染DOM内容,参数2:渲染的dom元素位置(获取DOM的方式),如:ReactDOM.render(mydiv,document.getElementById('app'));//这里app表示index.htlm文件中一个id值为app的标签,如<div id='app'></div>//4.webpack打包构建后,在dist目录下的文件是正常可以访问的。
JSX:
不难发现使用js创建元素的方式是非常繁琐的,因此这里介绍一款可以解决这个问题语法:JSX;
HTML 语言直接写在 JavaScript 语言中,不加任何引号,这就是 JSX 语法,它允许 HTML 与 JavaScript 的混写;
它是 一种 JavaScript 的语法扩展, 我们推荐在 React 中使用 JSX 来描述用户界面,JSX 是在 JavaScript 内部实现的;元素是构成 React 应用的最小单位,JSX 就是用来声明 React 当中的元素(底层实际就是通过上面js创建元素的)使用JSX语法时首先要安装:(cnpm install babel-preset-react -D)并配置在.babelrc文件中,babel-preset-react 用来转换JSX代码。(注意新版本:babel-preset-react-app,此环境应该基于上面环境)
与浏览器的 DOM 元素不同,React 当中的元素事实上是普通的对象,如:
// 注意:想要正常运行JSX语法:还需要以下两步://1.在项目目录下新建:.babelic文件,其配置代码如下:{"presets": ["env", "stage-0", "react"],"plugins": ["transform-runtime"]}//2.cnpm i babel-preset-env babel-preset-stage-0 babel-plugin-transform-runtime --save ,下载上面配置依赖的包。//3.配置完以上环境后,应该使用webpack打包后才可以以files的方式打正常访问react项目import React from 'react';import ReactDOM from 'react-dom';var titles = '这是一个提示';var elements =<div>{/* JSX语法中允许有一个根节点,根节点中可以嵌套其它元素 */}<p title={titles}>hello</p>{/*JSX中使用变量用{}包裹,实际指使用js语法时用{}包裹*/}<a href="#">hello</a><p className='textp'>hello</p>{/*在JSX中使用className代替class属性,因为class在js中只一个关键字*/}<label htmlFor="">hello</label>{/* 在JSX中for使用htmlFor代替 */}</div>const box = document.getElementById('box');ReactDOM.render(elements,box);//总结://1.当编译时遇到尖括号<>当JSX执行,当遇到大括号{}当js执行//2.JSX语法中使用className代替class属性//3.在JSX中for使用htmlFor代替//4.jsx语法中只能使用一个根元素//5.jsx中数组会自动展开
JSX 中注释:
写法一:
{// 注释// ...}
写法二(单行推荐):
{/* 单行注释 */}
写法三(多行推荐):
{/** 多行注释*/}
JSX 不同写法:
Babel 会把 JSX 通过React.createElement() 函数编译,每个 React 元素都是一个真实的 JavaScript 对象;下面几种方式是等价的,如:
const element = (<h1 className="greeting">Hello, world!</h1>);const element = React.createElement('h1',{className: 'greeting'},'Hello, world!');const element = {type: 'h1',props: {className: 'greeting',children: 'Hello, world'}};const element = {tagName:'h1',attrs:{className: 'greeting'},children:['Hello, world']}
组件:
React 允许将代码封装成组件,然后像插入普通 HTML 标签一样,在网页中插入这个组件即可使用;组件规则注意事项:组件名的第一个首字母必须大写,class声明式组件必须有 render 方法,组件内必须有且只有一个根节点,组件的属性可以在组件内通过props 获取(函数需要传递参数:props;类直接通过: this.props),如:
函数式声明组件(无状态):
名字不能用小写,React 在解析的时候,是以标签的首字母来区分的,如果首字母是小写则当作 HTML 来解析,如果首字母是大写则当作组件来解析。
// 函数名大写,组件名为函数名// 函数式声明组件,必须有return关键字,即使没内容也应该:return nullfunction Header(props){return (//当返回多行代码时,建议使用小括号括起来<div><p>这是header组件{props.name}</p>{/* 这里直接通过{属性名}的方式是不能拿到属性值的,需要给函数传递一个参数如:props等,在通过这个参数点出属性:{props.name},且这些属性只读 */}</div>)}const box = document.getElementById('box');// 通过 <函数名/> 的方式定义组件名;组件中传递参数使用属性的方式,如:name={变量名},age={变量名};分开传递参数很不方便,可以使用es6中属性扩散语法传递一个对象达到同样效果:var obj = {name:'jack',age:'28',gender:'男'}var cont = <div><Header {...obj}/> {/* es6中属性扩散语法 */}</div>ReactDOM.render(cont,box);
抽离组件:
上面组件声明在同一个js文件中,没有达到减少主文件代码量的问题,想要减少主文件代码量,就得将组件抽离出来,如:
// 被抽离的组件header.js文件:(扩展:组件可以使用jsx后缀名,但是需要在webpack.config.js文件中配置解析jsx文件的loader:loader和js一样,可连写为:( js|jsx)$ 、jsx?$ )import React from 'react';//导入React,注意:React首字母必须大写function Header(props){return (<div><p>这是header组件{props.name}</p></div>)}// 抽离组件需要暴露出:export default Header;//或直接在函数或类前面加 export default;// 在主文件(main.js)中使用这个组件:// 导入组件:import Header from './components/header.js';// 在主文件中使用 <Header><Header/> (简写:<Header/>)使用组件即可
类方式声明组件(有状态):
// 通过class定义一个Header组件,extends 继承了React中Componentclass Header extends React.Component { render() { //使用render函数,函数中返回组件类容:return (<div><p>这是header组件{this.props.names}</p>{/*class声明的组件拿传递的值通过:this.props点属性名,也是只可读的*/}</div>)}}; ReactDOM.render(<Header names='jack'/>, document.getElementById('box'));// 抽离组件的方式和函数声明组件的方式中一样
组件传值props和state:
state 和 props区别在于props 是不可变的,而 state 可以改变。函数式组件只能通过 props 来传递数据,不能通过state传值;class定义的组件都可以使用它们传值。
class Header extends React.Component { constructor(props){//class定义的类中如果实现了继承,默认就有constructor函数,只是看不见;当写出这个构造函数时,要通过super调用:super(props);//表示父类的构造函数console.log(props);//在constructor中是不能直接使用props的,想要使用,就得在constructor中传递props// constructor中的this.state表示私有数据对象,类似vue中data(){},this.state中定义的数据是可改变的,通过this.state点属性拿到,如:this.state={messages:'hello',names:'jack'}}render() { return (<div><p>这是header组件{this.props}</p><p>这是header组件{this.state.messages}</p><button onClick={this.change}>变更messages的值</button>{/* jsx中使用事件时,必须使用驼峰命名法,且处理函数名前面加this. */}</div>)}//React函数中this默认指向undefined,若想this指向class类,可以使用箭头函数:如:// change(){// this.state.messages = '修改hello为word';// }change=()=>{//这里是将箭头函数赋值给change,箭头函数中this依旧指向class类// this.state.messages = '修改hello为word';//通过this.state方式修改数据只是单向的,内存中数据是修改了,但是页面没有自动刷新,因此也不推荐,推荐setState()方法异步修改,如// this.setState({//setState()方法中传递一个对象,对象中传入要修改的属性,如:// messages:'word',// names:'lucky'// })// this.setState(function(prevState,props){//setState也支持传递一个函数,但是函数必须return一个对象;函数中第一个参数表示修改数据之前的数据,第二个参数是外界传递过来的数据属性// return {// messages:'word'// }// })this.setState(function(prevState,props){//setState()因为setState是异步修改数据的,想要确保拿到最新的数据,那么,可以给setState继续传递一个参数为:回调函数,在回调函数里面拿到的数据是比较保险的,如:return {messages:'word'}},function(){console.log(this.state.messages);})}}; ReactDOM.render(<Header/>, document.getElementById('box'));// 抽离组件的方式和函数声明组件的方式中一样
组件的生命周期:
组件从创建到运行再到销毁,这期间伴随着各种各样的事件,这些事件统称为组件的生命周期函数;
组件生命周期分为三部分:组件创建阶段(命周期函数一生只创建一次)、组件运行阶段(生命周期函数根据props和state的状态是否改变运行0-N次)、组件销毁阶段(生命周期函数一生只执行一次)。
import React from 'react';import ReactDOM from 'react-dom';import ReactTypes from 'prop-types';//导入数据校验模块class Header extends React.Component { constructor(props){super(props);//1.使用:this.state={}初始化组件私有状态,初始化组件私有数据this.state={messages:'hello',numstate:props.num//因为props中的值时只读的,想要修改值,就得使用state存值,但是state存的值是固定的,因此这里可以传递一个动态的值,即props的值,此时页面的值应该为对应的state值。}}//2.使用:static defaultProps = {}定义默认的值供程序正常运行,如:static defaultProps = {num:0}//3.使用:static propTypes = {}做数据校验,防止传过来的数据无效,如:static propTypes = {//特别注意:做校验需要安装一个包prop-types: npm install prop-types ,并在开头导入num:ReactTypes.number//如果需要检验其他数据类型,可阅读prop-types原文;当做完数据校验时,在组件中传递过来的值类型不符合时会报警告}// 4.使用:componentWillMount(){}组件即将被挂载到页面时触发函数,虚拟DOM也没创建(不能被拿到),因此此页面不能操作页面,可以操作数据,如:componentWillMount(){console.log(this.state.messages);//数据可被拿到console.log(this.props.num);//数据可被拿到this.myFunction();//函数可被调用console.log(document.getElementById('testp'));//不能被获取}// 5.使用:render(){}即将渲染内存中的虚拟DOM,该函数执行完后内存中已经渲染好数据了,但是还没有被挂载到页面上,如:render() { //render中不能使用this.setState方法,否则会进入死循环return (<div>{/* <p id='testp'>num的值:{this.props.num}</p> */}<p id='testp' onClick={this.addnumstate}>num的值自增为:{this.state.numstate}</p>{/*修改为可改变的state值*/}<span ref='spans'>hello</span>{/* 利用refs属性可以快速获取到该节点 */}</div>)}// 6.使用:componentDidMount(){}页面已经有可见的DOM元素了,此时可以拿到render中的DOM,如:componentDidMount(){console.log(document.getElementById('testp'));//可以获取DOM元素,可对其进行操作(原生DOM型,但是需要注意this指向问题,可使用箭头函数,但是不推荐原生方式),如:// document.getElementById('testp').οnclick=()=>{// this.setState({// numstate:this.state.numstate++// })// }//推荐React中事件绑定的方式,如testp标签中}//7.使用:conmponentWillReceiveProps(){}当子组件的属性props改变时触发此方法,但是第一次渲染时不会被触发,如:conmponentWillReceiveProps(nextProps){//这里的数据是旧的,但是通过第一个参数nextProps.属性名,拿到的是最新的数据console.log('当外界传递过来新的props时,才会触发该事件执行');console.log(next.Props.num);}addnumstate=()=>{//使用箭头函数改变this指向为类的实例;注意:在dom中掉用次函数时在函数名前加thisthis.setState({numstate:this.state.numstate++})}myFunction(){console.log('测试componentWillMount是否可以调用外部函数');}// 以上是组件创建阶段生命周期,下面将介绍组件运行阶段生命周期:// 1.使用:shouldComponentUpdate(){}里面return一个布尔值,判断是否要更新页面上数据,当为true时可以改变页面上数据,当为false时不能改变页面上的数据;无论是true或false,内存中数据都是改变重新渲染了shouldComponentUpdate(nextProps,nextState){//此方法中可传入两个参数:第一个表示最新的props,第二个表示最新的stateconsole.log(nextState.numstate);return true;//这里应该使用传入参数做出逻辑后return 布尔值}// 2.当shouldComponentUpdate中返回false时,会重新返回到shouldComponentUpdate执行,若果返回的是true,则执行下面的:componentWillUpdate(){}将要更新数据,此时页面和内存中数据都未被更新,如:componentWillUpdate(){//此时页面上数据都是旧的console.log(this.refs.spans.innerHTML);//使用refs获取ref设置的值}// 3.这里有一个运行时的render方法,但是里面数据h还是旧的。// 4.使用:componentDidUpdate(){}更新了页面和内存中的数据,此时数据都是最新的,如:componentDidUpdate(){//此时页面上数据都是新的console.log(this.refs.spans.innerHTML);}}; ReactDOM.render(<Header num='校验时传入无效数据时,会报警告'/>, document.getElementById('box'));
对上面组件生命周期流程总结如下图:
提示:本文图片等素材来源于网络,若有侵权,请发邮件至邮箱:810665436@qq.com联系笔者删除。
笔者:苦海