案例引入
在原生html中引入react,使用script标签引入三个js文件:
react.development.js、react-dom.development.js、babel.min.js
在需要用到react的语法中,sciprt标签必须设置为babel,因为react中的jsx语法不能被js识别,需要babel转化为js。
html设置一个容器,使用react的render方法即可在原生html中使用react。
JSX语法
jsx语法和js语法创建虚拟dom:
1 2 3 4
| const VDOM = React.createElement('h1',{id:'title'},'hello,world')
const VDOM = <h1 id='title'>hello,world</h1>
|
层级过深,使用js语法则会极其繁琐,而使用jsx更加清晰明了。
虚拟DOM的本质
console.log输出VDOM为一个一般的Object。可以得到:
虚拟DOM本质是Object类型的对象(一般对象)
虚拟DOM比较“轻”,真是DOM比较“重”,因为虚拟DOM是React内部再用,无需真是DOM上那么多的属性。
虚拟DOM最终会被React转化为真实DOM,呈现在页面上。
JSX语法规则
简单模板
1 2 3 4 5 6 7 8
| const id = "TeSt"
const VDOM =( <h2 id={id.toLowerCase()} className="title"> <span>hello,react</span> </h2> )
|
语法:
定义虚拟DOM时,不需要写引号
标签中混入JS表达式时要用{}
样式的类名指定不要用class,要用className
内联样式,要用style={{key:value}}
的形式去写
必须只有一个根标签
标签必须闭合
标签首字母
- 若小写字母开头,则将改标签转为html中同名元素,若html中无该标签对应的同名元素,则报错
- 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错
注意:一定要区分js语句(代码)和js表达式:
模块化与组件化
基本理解
模块
1.理解: 向外提供特定功能的js程序,一般就是一个js文件
2.为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
3.作用:复用js,简化js的编写,提高js 运行效率
组件
1.理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image)等等
2.为什么:一个界面的功能更复杂
3.作用:复用编码,简化项目编码,提高运行效率
模块化
当应用的js都以模块来编写的,这个应用就是一个模块化的应用。
组件化
当应用是以多组件的方式实现,这个应用就是一个组件化的应用
函数式组件
创建函数式组件
1 2 3 4 5 6 7
| // 1.创建函数式组件 function MyComponent(){ console.log(this)// 此处的this是undefined,因为babel编译后开启了严格模式 return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2> } // 2.渲染组件到页面 ReactDoM.render(<MyComponent/>,document.getElementById('test'))
|
执行了ReactDoM.render(…….之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
类式组件
类的基本知识
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Person { constructor(name,age){ this.name = name this.age = age } speak(){ console.log(`我叫${this.name},今年${this.age}岁`) }
class student extends Person { constructor(name,age,grade){ super(name,age) this.grade = grade } }
|
类的继承在js中是用原型链实现的,在此案例中,student实例的原型中存在Person里的方法,所以student实例是可以直接调用Person中的方法。
总结:
类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加指定属性时再写。
如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super时必须要调用的。
类中嗦定义的方法,都是放在了类的原型对象上,供实例去使用。
类式组件案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class MyComponent extentds React.Component{ render(){ return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2> } }
ReactDoM.render(<MyComponent/>,document.getElementById('test'))
|
起步预备知识
react中的事件绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Weather extends React.Component{ constructor(props){ super(props) this,state = {isHot:false} } render(){ const {isHot}=this.state return <h1 onclick={demo}>今天天气很{isHot ?'炎热':'凉爽'}</h1> } }
ReactDoM.render(<Weather/>,document.getElementById('test')) function demo(){ console.log('标题被点击了') };
|
类中方法的this指向
在原生js中,this指向window,如果开启严格模式,this为undefined。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Person { constructor(name,age){ this.name = name this.age = age } study(){ console.log(this); } } const pl= new Person('tom',18) p1.study() const x= p1.study x()
|
解决:
在构造器中对直接调用而非通过实例调用的方法加上一句:this.xxx = this.xxx.bind(this)
这句话的本质就是:将原型上的方法,this改到自身实例上,然后赋给实例
三大核心属性
state
相当于vue中的data。
state 是组件对象最重要的属性,值是对象(可以包含多个key-value 的组合)
组件被称为”状态机”,通过更新组件的 state 来更新对应的页面显示(重新渲染组件)
强烈注意
- 组件中render 方法中的 this 为组件实例对象
- 组件自定义的方法中this为undefined,如何解决?
- 强制绑定 this: 通过函数对象的 bind()
- 箭头函数(赋值语句+箭头函数)
- 状态数据,不能直接修改或更新
简单案例
1 2 3 4 5 6 7 8 9 10 11 12
| class Weather extends React.Component{{ constuctor(props){ super(props) this.state = isHot:false } render(){ console.log(this); return <h1>今天天气很{this.state.isHot?'炎热':'凉爽'}</h1> }
ReactDoM.render(<Weather/>,document.getElementById('test'))
|
关于this指向问题的解决
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 Weather extends React.component{ constructor(props){ super(props) this.state ={isHot:false,wind:'微风'} this.changeWeather =this.changeWeather.bind(this) } render(){ const {isHot,wind} = this.state return <h1 onclick={this.changeweather}>今天天气很{isHot ?'炎热':'凉爽'},{wind}</h1> } changeWeather(){ const isHot = this.state.isHot this.setstate({isHot:!isHot}) }
ReactDoM.render(<Weather/>,document.getElementById('test'))
|
简写方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Weather extends React.Component{ state ={isHot:false,wind:'微风'} render(){ const {fisHot,wind} = this.state return <h1 onclick={this.changeweather}>今天天气很{isHot ?'炎热':'凉爽'},{wind}</h1> changeWeather = ()=>{ cosnt isHot = this.state.isHot this.setstate({isHot:!isHot}) }
ReactDoM,render(<Weather/>,document.getElementById('test'))
|
props
理解
每个组件对象都会有props(properties的简写)属性;组件标签的所有属性都保存在props中。
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Person extends React.Component{render(){ console.log(this); const {name,age,sex}= this.props return( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age}</li> </ul> ) }
ReactDoM.render(<Person name="jerry" age="19" sex="男"/>,document.getElementById('test1') ReactDoM,render(<Person name="tom" age="18" sex="女"/>,document.getElementById('test2')) ReactDoM,render(<Person name="老刘" age="30" sex="女"/>,document.getElementById('test3'))
constp={name:'老刘',age:18,sex:'女'} ReactDoM.render(<Person {...p}/>,document.getelementById( 'test4'))
|
对props进行限制
React实现了限制props属性的工具类PropTypes,在15版本及以前,PropTypes内置在React核心库,可以直接React.PropTypes.xxx进行使用。但是从16版本开始,需要额外引入prop-type.js文件才能使用;引入后全局多了一个对象PropTypes,使用该规则规定props类型后,如果类型错误控制台会出现warning警告。
案例
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
| class Person extends React.Component{render(){ render(){ console.log(this); const {name,age,sex}= this.props return( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age}</li> </ul> ) } }
Person.propTypes = { name:PropTypes.string.isRequired, sex:PropTypes.string, age:PropTypes.number, speak: PropTypes.func }
Person.defaultProps = { sex:'不男不女', age:18 } constp={name:'老刘',age:18,sex:'女'} ReactDoM.render(<Person {...p}/>,document.getelementById( 'test4'))
|
props的简写方式
props是只读的,无法修改,this.props.xxx = ‘xxx’此类代码会报错。
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
| class Person extends React.Component{render(){ render(){ console.log(this); const {name,age,sex}= this.props return( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age}</li> </ul> ) } static propTypes = { name:PropTypes.string.isRequired, sex:PropTypes.string, age:PropTypes.number, speak: PropTypes.func } static defaultProps = { sex:'不男不女', age:18 } }
constp={name:'老刘',age:18,sex:'女'} ReactDoM.render(<Person {...p}/>,document.getelementById( 'test4'))
|
关于构造器
constructor一般不用写,按文档,描述仅用于一i西安两种情况:
- 通过this.state赋值对象来初始化内部state,如this.state={a:’b’}
- 为事件处理函数绑定实例,如:this.changeWeather = this.changeWeather.bind(this)
构造器写了一定要调用super,且一定要传props。如果写了构造器又没有接收props,传递给super,则this.props无法获取到props。
函数式组件使用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
| function Person(props){ const{name,age,sex}= props return ( <ul> <li>姓名:{name}</li> <li>性别:{sex}</li> <li>年龄:{age}</li> </u1> ) }
Person.propTypes={ name:PropTypes.string.isRequired, sex:PropTypes.string, age:PropTypes.number, }
Person.defaultProps= { sex:男',//sex默认值为男 age:18 //age默认值为18 } //渲染组件到页面 ReactDOM,render(<Person name="jerry"sex="女" age={18}/>,document.getElementById('test1')
|
refs
用来获取真实DOM(虚拟DOM转为真是DOM之后真真实实的节点)。
基本案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| class Demo extends React.Component{ showData =()=>{ const {input1}= this.refs alert(input1.value) } showData2=()=>{ const {input2}= this.refs alert(input2.value) } render(){return( <div> <input ref="input1" type="text" placeholder="点击按饥提示数据"/> <button onclick={this,showData}>点我提示左侧的数据</button> <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数"/> </div> } }
ReactDOM.render(<Demo/>,document.getElementById('test')
|
回调形式ref
字符串ref在官网上提到不建议使用,会出一些问题(没有明说,只给出了github的讨论页),建议使用回调形式ref。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Demo extends React.Component{ showData =()=>{ const {input1}= this alert(input1.value) }
render(){return( <div> <input ref={c => this.input1 = c} type="text" placeholder="点击按饥提示数据"/> <button onclick={this.showData}>点我提示左侧的数据</button> } }
ReactDOM.render(<Demo/>,document.getElementById('test')
|
回调ref的调用次数
官网描述:如果 ref 回调函数是以内联函数
的方式定义的,在更新过程中它会被执行两次,第一次传入参数 nu11,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式
可以避免上述问题,但是大多数情况下它是无关紧要的。
附:关于jsx里边写注释:{/* <input/> */}
解决上述问题:这种方式写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
| class Demo extends React.Component{ state = {isHot:false} saveInput =()=>{ const {input1}= this alert(input1.value) } changeWeather = (c)=>{ this.ipnut1 = c console.log('@',c) }
render(){ const {isHot} = this.state return( <div> <input ref={this.myRef} type="text"placeholder="点击按饥提示数据"/> <button onclick={this.showData}>点我提示左侧的数据</button> <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="点击按钏提示数据"/> </div>) } }
ReactDOM.render(<Demo/>,document.getElementById('test')
|
creatRef
React.createRef调用后可以返回一个容器,该容器可以存储被ref标识的节点。
注意:该容器时专人专用的,一个容器只能装一个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
| class Demo extends React.Component{ myRef = React.createRef() myRef2 = React.createRef() showData=()=>{ alert(this.myRef.current.value); } showData2=()=>{ alert(this.myRef2.current.value); } render(){ return( <div> <input ref={this.myRef} type="text" placeholder="点击按饥提示数"/> <button onclick={this.showData}>点我提示左侧的数据</button> <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据"/> </div> ) } }
ReactDOM.render(<Demo/>,document.getElementById('test')
|
补充知识
react中的事件处理
react中的事件均是经过封装,并没有直接使用原生的事件。
- 通过onXxx(驼峰)属性指定事件处理函数(注意大小写)
- React使用的是自定义(合成)事件,而不是使用的原生DOM事件——是为了更好的兼容性
- React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)——为了高效
- 通过event.target得到发生事件的DOM元素对象——不要过度使用ref
受控组件与非受控组件
非受控组件:现用现取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Login extends React.Component{ handlesubmit=(event)=>{ event.preventDefault() const {username,password}=this alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`) } render(){ return ( <form onSubmit=fthis.handleSubmit}> 川户名:<input ref={c =>this.username = c} type="text" name="username"/> 密码:<input ref={c =>this.password = c} type="password" name="password"/> <button>登录</button> </form>) } }
ReactDoM.render(<Login/>,document.getElementById('test'))
|
受控组件:维护状态(vue中的双向绑定)
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
| class Login extends React.Component{ state ={ username:'', password:'', } saveUsername =(event)=>{ this.setstate({username:event.target.value}) } savePassword =(event)=>{ this.setstate({password:event.target.value}) } handleSubmit =(event)=>{ event.preventDefault() const {username,password}=this.state alert(`你输入的用户名是:${username},你输入的密码是:${password}`) } render(){ return( <form onsubmit={this.handlesubmit}> 用户名: <input onchange={this.saveUsername} type="text" name="username"/> 密码:<input onchange={this.savePassword} type="password" name="password"/> <button>登求</button> </form> ) }
ReactDoM.render(<Login/>,document.getElementById('test'))
|
高阶函数&函数柯里化
高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
- 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
- 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有哪些:Promise、setTimeout、arr.map()等
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
对上一个案例进行优化:
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
| class Login extends React.Component{ state ={ username:'', password:'', } saveFormData=(dataType)=>{ return(event)=>{ this.setstate({[dataType]:event.target.value}) } } handleSubmit =(event)=>{ event.preventDefault() const {username,password}=this.state alert(`你输入的用户名是:${username},你输入的密码是:${password}`) } render(){ return( <form onsubmit={this.handlesubmit}> 用户名: <input onchange={this.saveFormData('username')} type="text" name="username"/> 密码:<input onchange={this.saveFormData('password')} type="password" name="password"/> <button>登求</button> </form> ) }
ReactDoM.render(<Login/>,document.getElementById('test'))
|
生命周期
- 组件从创建到死亡它会经历一些特定的阶段。
- 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
| class Life extends React.Component{ state ={opacity:1} death=()=>{ ReactDOM.unmountComponentAtNode(document.getElementById('test')) } componentDidMount(){ this.timer = setInterval(()=>{ let {opacity} = this.state opacity -= 0.1 if(opacity <= 0) opacity=1 this.setState({opacity}) },200) } componentWillUnmount(){ clearInterval(this.timer) } render(){ console.log('render'); return( <div> <h2 style={{opacity:this.state.opacity}}>React学不会怎么办?</h2> <button onclick=fthis.death}>不活了</button> </div> ) } }
ReactDoM.render(<Life/>,document.getElementById('test'))
|
生命周期函数
旧版本生命周期
挂载时:constructor -> componentWillMount -> render -> componentDidMount -> componentWillUnmount
父组件render:componentWillReceiveProps ->shouldComponentUpdate(setState) -> componentWillUpdate(forceUpdate) -> render ->componentDidUpdate -> componentWillUnmount
componentWillReceiveProps(组件将要收到props)存在一个坑,第一次调用时不算,第一次调用时不会执行,一般是手动传入props操作调用。
shouldComponentUpdate,控制组件更新的阀门,必须返回一个布尔值,为false则不会更新组件。
总结:
- 初始化阶段:有ReactDOM.render()触发—初次渲染
- constructor()
- componentWillMount()
- render()
- componentDidMount():常用,一般在这个钩子做一些初始化的事:如请求、订阅消息、开启定时器等
- 更新阶段:由组件内部this.setState()或父组件重新render触发
- shouldComponentUpdate()
- componentWillUpdate()
- render()
- componentDidUpdate (preProps,preState)
- 卸载组件:由ReactDOM.unmounComponentAtNode()触发
- componentWillUnmount():一般在这个钩子做一些收尾的事,如关闭定时器、取消订阅等。
新生命周期
在新版本中,这三个钩子要加上UNSAFE_前缀:componentWillMount、componentWillUpdate、render:componentWillReceiveProps 。不加前缀调用会报弃用警告。
getDerivedStateFromProps(props,state):必须加上static使之为静态方法;必须有返回值。使用场景:state的值在任何时候都取决于props。
getSnapshotBeforeUpdate(prevProps,prevState):必须返回一个null或者snapshot value。在最近一次渲染输出(提交到DOM系欸但)之前调用,使得组件能在发生更改之前从DOM中捕获一些信息。
总结:
- 初始化阶段:有ReactDOM.render()触发—初次渲染
- constructor()
- getDerivedStateFromProps()
- render()
- componentDidMount():常用,一般在这个钩子做一些初始化的事:如请求、订阅消息、开启定时器等
- 更新阶段:由组件内部this.setState()或父组件重新render触发
- getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate (preProps,preState)
- 卸载组件:由ReactDOM.unmounComponentAtNode()触发
- componentWillUnmount():一般在这个钩子做一些收尾的事,如关闭定时器、取消订阅等。
虚拟DOM与DOM diff算法
虚拟DOM中key的作用:
- 简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
- 详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 若虚拟D0M中内容没变,直接使用之前的真实DOM
- 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- 旧虚拟DOM中未找到与新虚拟DOM相同的key
- 根据数据创建新的真实DOM,随后渲染到到页面
用index作为key可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新==>界面效果没问题,但效率低。
- 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==>界面有问题。
- 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
开发中如何选择key?
- 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
- 如果确定只是简单的展示数据,用index也是可以的。