react是将数据渲染为HTML视图的开源JS工具。
react的优势:原生js或jq操作的是真实的DOM(Document Object Model,文档对象模型,可指html模型中的对象),操作频繁且无法复用。而react是操作虚拟DOM,且有着优秀的diffing(different)算法,易于比较出有差异的一部分、复用真实DOM、最小化页面重绘。
主要先分2步,创建虚拟DOM。然后把虚拟DOM渲染到页面(其实就是虚拟DOM被react最终转换为真实DOM显示上页面)
js
<script type="text/babel">//说明用的语言不是js是jsx并且使用babel翻译
//1.创建虚拟DOM
const VDOM = (
<h1>hello,react</h1>
)
//2.渲染虚拟DOM到页面上
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
虚拟DOM创建2种书写方式。js或者jsx。但jsx其实就是js(创建DOM太繁琐)的语法糖,就是比js创建方式更加便捷的方式,底层还是用了js创建。
jsx的语法: //不可出现双{所以以下用代码框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 1. 定义虚拟DOM时,不要写引号。2. 标签中混入js表达式时要用{}。3. 样式类名的指定不要用class ,应该使用className 。4.内联样式,要用style ={{key:value,key:value}}形式,value可以是字符串或对象。5. 虚拟DOM只能有一个根标签,想写多个只能是用个div啥的包起来。6. jsx中标签必须闭合。7. jsx的标签首字母 (1 ).若小写字母开头,则将该标签转为html同名元素,若html中无该标签对应同名元素则报错。 (2 ).若大写字母开头,react就去渲染对应的组件,若组件没定义则报错。
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" > <title > hello_react</title > <style > .title { background-color : orange; } </style > </head > <body > <div id ="test" > </div > <script type ="text/javascript" src ="./js/react.development.js" > </script > <script type ="text/javascript" src ="./js/react-dom.development.js" > </script > <script type ="text/javascript" src ="./js/babel.min.js" > </script > <script type ="text/babel" > const val = "hello,react" const VDOM = ( <div > <h1 className ="title" style ={{color: "red "}}> {val}</h1 > <h1 > hello,react</h1 > </div > ) ReactDOM.render(VDOM,document .getElementById("test" )) </script > </body > </html >
注意:{}里面只能是js表达式,不能是js语句(代码)。
js表达式:(其实就是有返回值的就是表达式)
1.a 变量
2.a+b 变量运算结果
3.demo(0) 方法
4.arr.map() 数组的使用方法
5.function test(){} 直接定义的方法也是会有返回值的,就是方法本身
js语句(代码):
1.if(){}
2.for(){}
3.switch(){case:}
数组map()方法的定义:map() 方法返回一个由原数组中的每个元素调用一个指定方法后的返回值组成的新数组。
map使用实例:
1 2 3 4 5 6 7 8 9 10 const data=["a","b","c"] const VDOM = ( <ul > { data.map((item,index)=>{ return <li key ={index} > {item}</li > //需要为每个节点赋予一个唯一的key,用于diffing算法。 }) } </ul > )
react面向组件开发:
模块一般在前端仅指js模块,也就是个完成特定功能的js程序,一般就是个.js文件。
而组件则是比模块更深,是指实现某局部功能效果的代码和资源集合包括html/css/js/image等等。
模块和组件都是为了复用代码,简化编码,提高开发效率。
模块化:如果此应用的js都是以模块来编写,则此应用为模块化应用。
组件化:如果此应用是以多组件形式完成,则此应用为组件化应用。
var,const,let
const和let的作用域一样,是块{}作用域 (即{}内定义的不可被外部访问到)。且const不允许修改其所指向的内存地址中的内容 ,也就是当const一个基本类型(数值、字符串、布尔值)时,基本类型的值就存在指向的内存空间中,所以值不可修改。但当const一个复合类型例如数组、对象时,const变量指向的仅是那个复合类型的指针,所以不然让其重新指向但可以修改指向的指针中的值(即可修改对象的变量)。const 必须初始化赋值。
var定义的变量可以修改,如果不初始化会输出undefined,不会报错。var的作用域为函数级作用域,其实就是和平时java里使用的变量一样,定义在哪就决定是全局变量或函数的局部变量,且会去找作用域最近的同名变量(即函数内部使用会优先去找函数内部是否有同名变量,没有才去找全局同名变量)。且var有变量提升机制(即使用var在函数或全局内任何地方声明变量相当于在其内部最顶上声明它,应注意提升的只是函数的声明语句,赋值还是在原来位置所以之前使用还是会undefined)
let为块级作用域,其余和var的使用相似。
react面向组件开发: 函数式组件 1 2 3 4 5 6 7 8 9 10 11 <script type="text/babel" > function MyComponent ( ) { console .log(this ) return <h2 > Hello</h2 > } ReactDOM.render(<MyComponent /> ,document .getElementById("test" )) </script>
需注意:(适用于简单组件)
一.定义的函数一定得大写首字母,因为jsx的语法规定:
1.标签首字母小写则直接转换为html同名元素。
2.标签首字母大写react才会去作为组件渲染。
二.传给react render时得用标签形式去传,不能直接调用方法,这样react不会将其渲染为组件。
三.关于this的指向:在text/babel里的代码会由babel翻译,而babel本身会启动严格模式,会禁止自定义函数里的this指向windows。
执行render发生的事:
1.react解析标签,找到组件。
2.发现组件是函数定义则调用此函数,将返回的虚拟DOM转化为真实DOM显示在页面上。
类式组件 函数式组件的组件名就是函数名,类式组件的组件名就是类名。所以renact render时传入的标签为类名
1 2 3 4 5 6 7 8 9 10 11 <script type="text/babel" > class MyComponent extends React .Component { render ( ) { console .log(this ) return <h2 > Hello lei</h2 > } } ReactDOM.render(<MyComponent /> ,document .getElementById("test" )) </script>
需注意:(适用于简单组件)
一.类式组件一定要继承React.Component类
二.不一定要写构造方法,但一定要重写其render方法(此方法和渲染到页面上的render没啥关系就是同名而已),且此方法返回组件。
执行render发生的事:
1.react解析标签,找到组件。
2.发现组件是类定义则new出该类的实例对象(即为组件实例对象),通过该实例对象调用其原型上的render方法。
3.将render方法返回的虚拟DOM转化为真实DOM显示在页面上。
什么是简单组件什么是复杂组件?
有state状态就是复杂组件,没有即为简单组件。
state是什么?
组件实例的三大核心属性 state 状态里存有数据,可根据其中数据驱动页面的变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <script type="text/babel" > class Weather extends React .Component { constructor (props ) { super (props) this .state={isHot :false } } render ( ) { return ( <h1 > 天气很{this.state.isHot?"炎热":"凉爽"}</h1 > ) } } ReactDOM.render(<Weather /> ,document .getElementById("test" )) </script>
jsx里的点击事件:
原生点击事件有三种实现方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <body> <button id ="btn1" > 按钮1</button > <button id ="btn2" > 按钮2</button > <button onclick ="demo()" > 按钮3</button > <script type ="text/javascript" > const btn1 = document .getElementById('btn1' ) btn1.addEventListener('click' ,()=> { alert('按钮1被点击了' ) }) const btn2 = document .getElementById('btn2' ) btn2.onclick = ()=> { alert('按钮2被点击了' ) } function demo ( ) { alert('按钮3被点击了' ) } </script > </body>
箭头函数:ES6的新规范(其实就是为了简化代码的匿名函数,即箭头函数可以替代函数表达式但是不能替代函数声明 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 1. 无花括号和return 的,只能包含一个表达式并且会将其作为返回值。当只有一个传入参数使,也可以再省略小括号() x => x + 6 相当于 function (x ) {return x + 6 ;} var fn = function (a, b ) {return a+b}const fn=(a,b ) => a+b;2. 有花括号和return 的,可以包含多条语句()=>{} 相当于 function ( ) {}var foo = function ( ) { var a=20 ; var b= 30 ; return a+b; } const foo=()=> { const a= 20 ; const b=30 ; return a+b }
但在这三种方式中jsx推荐使用第三种。没那么繁琐。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <script type="text/babel" > class Weather extends React .Component { constructor (props ) { super (props) this .state={isHot :false } } render ( ) { return ( <h1 onClick ={changeWeather} > 天气很{this.state.isHot?"炎热":"凉爽"}</h1 > ) } } ReactDOM.render(<Weather /> ,document .getElementById("test" )) function changeWeather ( ) { console .log("sbwy" ) } </script>
需要注意:jsx的方法名和原生不同,不是onclick是onClick。且对于onClick方法的赋值语句的花括号内得是方法名不可加括号,否则会直接运行changeWeather方法并将changeWeather方法的返回值给onClick了,应该给的是changeWeather这个方法在点击事件发生时被调才对。
点击事件的获取组件对象实例:
当要在changeWeather方法中取到实例组件对象又出现困难,首先是这个onClick绑定的方法应该和组件写在一起,分开写有点步骤不清晰。
但当将changeWeather方法写到类中时,还是找不到this对象,console.log显示undefined。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Weather extends React .Component { constructor (props ) { super (props); this .state = { isHot : false }; } render ( ) { return ( <h1 onClick ={this.changeWeather} > 天气很{this.state.isHot ? "炎热" : "凉爽"} </h1 > ); } changeWeather ( ) { console .log(this ) } }
原因/需知:
1.在类中调用自己实例的方法要用this.changeWeather。
2.但是因为是绑定onClick方法,此时是系统调用onClick方法的并没有使用实例对象(之前的构造函数一定是用实例对象调用,渲染时调用render方法也是规定用实例对象来调用,所以这两个方法内可以去到this,但onClick没有这种规定)。
3.因为类中方法会被默认开启局部严格模式,且babel也会开启严格模式,则此时this必然连window也获取不到。
bind方法:(其实此方法就是会创建一个指定this环境对象的相同函数,将其返回 )
当我们调用某些函数的时候是要在特定环境下才能调用到,所以我们就要把函数放在特定环境下,就是使用bind把函数绑定到特定的所需的环境下,具体来说就是改变此函数中的this指向。
此时应该在构造方法中创建一个绑定组件实例对象的changeWeather函数,并将次函数返给实例作为成员变量,则此时调用this.changeWeather函数时寻找原型链就会先找到实例对象的changeWeather函数,成功。
对于state的修改不可以直接修改(this.state.xx=xx)
必须使用组件原型链上的api setState来修改,且这种修改是一种更新,只会对同名state更改,其余不改变。只有使用组件提供的setState函数来修改state的值,react才会根据state的改变重新执行render方法生成对应虚拟DOM组件。(即每次setState则会重新再执行一次render)
小总结:
construct方法一般用于:1.初始化state。2.还可以修改this指向。
render方法一般用于:根据state值生成组件
最终:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Weather extends React .Component { constructor (props ) { super (props); this .state = { isHot : false }; this .changeWeather= this .changeWeather.bind(this ) } render ( ) { return ( <h1 onClick ={this.changeWeather} > 天气很{this.state.isHot ? "炎热" : "凉爽"} </h1 > ); } changeWeather ( ) { const isHot = this .state.isHot this .setState({isHot :!isHot}) } }
事实上还可以对以上代码进行简化:
原理:
1.如果不需要接收参数,则可以在类中直接定义或初始化变量(这些变量属于实例化对象)不需要写construct函数。
2.大多数情况下类中的自定义函数的环境this都会是实例化对象,所以可以用赋值语句形式+箭头函数来定义(因为箭头函数为匿名函数,本身没有this,但使用this也不会报错,它将外层的this(即实例化对象)作为自己的this)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Weather extends React .Component { state={isHot :true } render ( ) { return ( <h1 onClick ={this.changeWeather} > 天气很{this.state.isHot ? "炎热" : "凉爽"} </h1 > ); } changeWeather= ()=> { const isHot = this .state.isHot this .setState({isHot :!isHot}) } }
state总结:
state是组件对象中最重要的属性,state的值是对象,此对象可包含怼哥键值对。state={isHot:true,wind:3}
组件被称为状态机,即可以通过更新状态来重新渲染改变组件。
组件类中自定义的方法的this为undefined,2种方法绑定实例对象:
a.bind方法强制绑定后作为实例对象的函数
b.赋值语句+箭头函数
为更新组件,状态数据一定不能直接更改,得使用setState方法来更改。
props 可以直接从组件类的外部(标签处)获取信息。
例如:
ReactDOM.render( , document.getElementById(“test”));
则对象中的props就会存有对应键值对信息:
解构赋值:
对象的解构赋值其实就是找同名属性进行对应赋值:
1 2 3 let {bar,foo} = {foo :"aaa" ,bar :"bbb" }foo bar
批量传递props:
需要使用展开运算符…
预备知识:一般用于数组求和的reduce方法的介绍:
1 arr.reduce((prev,cur,index,arr )=> {},init)
arr为原数组
prev为上一次内部方法回调时的返回值,如果没有设置init则prev初值为0+arr[0],否则为init,所以有初值init会比没初值多加一次。
cur当前正在处理的数组元素。
index表示正在处理的数组元素索引,没初值则从1开始,有初值则从0开始。
init为prev的初值。
js展开运算符只能用于展开数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <script> let arr1 = [1 ,3 ,5 ,7 ,9 ]let arr2 = [2 ,4 ,6 ,8 ,10 ]console .log(...arr1)let arr3 = [...arr1,...arr2]function sum (...numbers ) { return numbers.reduce((prev,cur )=> { return prev+cur }) } let person = {name :"tom" ,age :18 }let person2 = {...person}let person3 = {...person,name :"jack" ,address :"地球" }</script>
但必须认清,批量传入的props和以上的都不同,并不属于对象赋值用法。
jsx标签内的{}就只是表明这是个js表达式,只是在react和babel双重影响下,…person 允许被使用了但也仅能用于标签内的展开对象传入参数这一种用法。
1 ReactDOM.render(<Person {...person }/> , document .getElementById("test" ));
对传入的props属性参数进行限制,例如哪个属性必须为什么类型,不可以为空,不传的默认值是什么。
因为被react认为没那么必要,所以被额外放在一个工具库prop-types.js包内,导入则全局多了个PropTypes对象,可用来限制props。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <script type="text/javascript" src="./js/prop-types.js" ></script> <script type ="text/babel" > class Person extends React .Component { state = { isHot : true }; render ( ) { console .log(this ); const { name, age, sex } = this .props; return ( <ul > <li > name:{name}</li > <li > age:{age}</li > <li > sex:{sex}</li > </ul > ); } } Person.propTypes = { name : PropTypes.string.isRequired, sex : PropTypes.string, age : PropTypes.number, speak :PropTypes.func }; Person.defaultProps = { sex : "不男不女" , age :18 }; function speak ( ) { console .log("我说话了" ) } const person = { name : "jerry" }; ReactDOM.render(<Person {...person } speak ={speak}/ > , document .getElementById("test" )); </script >
props是只读的,不允许修改会报错。
props的简写:其实之前的Person.propTypes=xxx,就是在给Person类添加属性,所以可以把这两个写在外部的添加属性写到Person类的内部,当没有关键字直接在类中 a = 1 是给实例对象添加属性 ,只需在此前添加关键字static,static a = 1,即为给类添加属性 ,则此时react就可以根据添加的属性限制属性来帮忙限制传入的props。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Person extends React .Component { state = { isHot : true }; static propTypes = { name : PropTypes.string.isRequired, sex : PropTypes.string, age : PropTypes.number, speak : PropTypes.func, }; static defaultProps = { sex : "不男不女" , age : 18 , }; render ( ) { console .log(this ); const { name, age, sex } = this .props; return ( <ul > <li > name:{name}</li > <li > age:{age}</li > <li > sex:{sex}</li > </ul > ); } }
组件类的构造器函数,就是基本不用写。因为构造器主要的两种功能:初始化state和修改自定义方法的this指向,我们已经可以用更简便的方式实现。
函数式组件没有this肯定无法使用state和refs,但是因为函数可以接收参数,所以可以使用props,直接在function Person(props)接收即可,react都帮忙把标签里的属性打包好了,直接在function中正常解构赋值取来使用即可。
函数式组件的props属性限制则只能写在外部,使用最开始的方法,直接给这个Person组件添加上propTypes、defaultProps属性即可Person.propTypes。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <script type="text/javascript" src="./js/prop-types.js" ></script> <script type ="text/babel" > function Person (props ) { const { name, age, sex } = props; return ( <ul > <li > name:{name}</li > <li > age:{age}</li > <li > sex:{sex}</li > </ul > ); } Person.propTypes = { name : PropTypes.string.isRequired, sex : PropTypes.string, age : PropTypes.number }; Person.defaultProps = { sex : "男" , age : 98 , }; const person = { name : "jerry" }; ReactDOM.render(<Person {...person } /> , document .getElementById("test" )); </script >
总结:
state是组件内部的事。props是从组件外获取到的。
props是只读的,不可以被修改会报错。
props是根据标签属性来封装出来的,类式组件封装在实例对象的props属性中,函数式组件就直接封装在传入的参数props中。
pros属性值在标签中可以由{…对象}来批量传入。
对props的限制,不一定非要有,所以react把此功能拿出封装成另一个包,需要限制时引入。
限制就是给组件类/组件那个function 添加属性propTypes或defaultProps,这样react接收标签属性封装为props时就会自动依此做出限制判断。
refs refs其实很类似与id(id也是标识一下,然后document.getElementById()),就是为了标识某一个标签,只要此标签被ref标识,则react就会将其收入到组件的refs属性里面,可以直接根据refs解构赋值获得那个标签本身,注意获得到的是真实DOM。
字符串形式的refs 注意标签里是ref,但存到组件是存到refs里的。(这种ref有点过时,编写简便但运行效率偏低但还是用得挺多)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <script type="text/babel" > class Person extends React .Component { output1=()=> { const {input1} = this .refs alert(input1.value) } output2=()=> { const {input2} = this .refs alert(input2.value) } render ( ) { console .log(this ); return ( <div > <input ref ="input1" type ="text" /> <button onClick ={this.output1} > 输出</button > <input ref ="input2" onBlur ={this.output2} type ="text" /> </div > ); } } ReactDOM.render(<Person /> , document .getElementById("test" )); </script>
回调refs 其实就是在对应要标记的标签上给ref属性添加个回调函数,因为ref属性绑定函数会给此函数传入参数(即为此标签节点)并帮你回调执行,所以直接在此回调函数中将接受到的当前标签节点并赋值到组件实例对象的一个属性 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <script type="text/babel" > class Person extends React .Component { output1=()=> { const {input1} = this alert(input1.value) } output2=()=> { const {input2} = this alert(input2.value) } render ( ) { console .log(this ); return ( <div > <input ref ={currentNode => this.input1=currentNode} type="text"/> <button onClick ={this.output1} > 输出</button > <input ref ={currentNode => this.input2=currentNode} onBlur={this.output2} type="text"/> </div > ); } } ReactDOM.render(<Person /> , document .getElementById("test" )); </script>
createRef 就是在类中定义实例的属性来接收React.createRef()方法传来的标签节点容器 ,但一定要注意这个容器是专人专用,也就是一个容器只能存放一个标签节点否则会被覆盖。然后再标签的ref属性中传入此容器{this.myRefContainer},此时此标签节点就会存储到myRefContainer中,可用.current的形式取出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <script type="text/babel" > class Person extends React .Component { myRef1=React.createRef() myRef2=React.createRef() output1=()=> { const input1 = this .myRef1.current alert(input1.value) } output2=()=> { const input2 = this .myRef2.current alert(input2.value) } render ( ) { console .log(this ); return ( <div > <input ref ={this.myRef1} type ="text" /> <button onClick ={this.output1} > 输出</button > <input ref ={this.myRef2} onBlur ={this.output2} type ="text" /> </div > ); } } ReactDOM.render(<Person /> , document .getElementById("test" )); </script>