React简介
什么是react?
react是一个构建用户可视化组件的JavaScript库,通虚拟DOM有效的控制真实DOM,遵循数据从高阶组件流向低阶组件的单向数据流。
为什么要学习react?
1、原生JavaScript操作DOM繁琐、效率低(DOM-API操作UI)
2、使用JavaScript直接操作DOM,浏览器会进行大量的重绘、重排。
3、原生JavaScript没有组件化编码方案,代码复用率低。
react的特点?
1、采用组件化模式、申明式编码,提高开发效率及组件复用率。
2、在react native中可以使用react语法进行移动端开发。
3、使用虚拟DOM和优秀diffing算法,尽量减少与真实DOM的交互。
JSX语法
JSX 是 javascript 的语法扩展。它就像一个拥有 javascript 全部功能的模板语言。它生成 React 元素,这些元素将在 DOM 中呈现。React 建议在组件使用 JSX。在 JSX 中,我们结合了 javascript 和 HTML,并生成了可以在 DOM 中呈现的 react 元素。
- 定义虚拟 dom 时不要用引号
- 标签中引入 js 表达式要用 {}
- 如果在 jsx 要写行内样式需要使用 style={{coler:red}} 形式
- 样式的类名指定不能写 class,要写 className;
- 只有一个根标签
- 标签必须闭合
- 标签首字母
①若小写字母开头,则会将该标签转为 html 同名标签,如果没找到,则会报错;
②若大写字母开头,则会认为是组件,它就会去找对应的组件,如果没找到,就会报组件未定义的错误;
实质:JSX 通过 babel 编译,而 babel 实际上把 JSX 编译给 React.createElement() 调用
React的基本使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
**
<div id="test">123</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"> //此处一定要写babel
*//1、创建虚拟DOM*
const VDOM=<h1>hello,react</h1> *//此处一定不要写引号,因为不是字符串*
*//2、渲染虚拟DOM到页面上*
ReactDOM.render(VDOM,document.getElementById('test'))
</body>
</html>
虚拟DOM的两种创建方式
1、使用jsx创建虚拟DOM
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</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"> //此处一定要写babel
*//1、创建虚拟DOM*
const VDOM=(<h1 id='title'>
<span>hello,react</span>
</h1> )*//此处一定不要写引号,因为不是字符串*
*//2、渲染虚拟DOM到页面上*
ReactDOM.render(VDOM,document.getElementById('test'))
</body>
</html>
2、使用js创建虚拟DOM
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</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">
*//1、创建虚拟DOM*
const VDOM=React.creatElement('h1',{id:"title"},React.creatElement('span',{},'hello,React'))
*//2、渲染虚拟DOM到页面上*
ReactDOM.render(VDOM,document.getElementById('test'))
</body>
</html>
3、虚拟DOM与真实DOM的区别
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>虚拟DOM与真实DOM</title>
</head>
<body>
**
<div id="test"></div>
<div id="demo"></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"> //此处一定要写babel
*//1、创建虚拟DOM*
const VDOM=(<h1 id='title'>
<span>hello,react</span>
</h1> )*//此处一定不要写引号,因为不是字符串*
*//2、渲染虚拟DOM到页面上*
ReactDOM.render(VDOM,document.getElementById('test'))
console.log("虚拟DOM",VDOM);
const TDOM=document.getElementById('demo')
console.log("真实DOM",TDOM)
*// 关于虚拟DOM:*
*// 1、本质上object类型的对象*
*//2、虚拟DOM比较属性较少,真实DOM属性多,欣因为虚拟DOM是react内部在使用,无需真实DOM那么多属性*
*//3、虚拟DOM最终会被React转换为真实DOM,呈现在页面上。*
</script>
</body>
</html>
模块与组件、模块化与组件化的理解
模块
1)理解:向外提供特定功能的js程序,一般就是一个js文件
2)为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂
3)作用:复用js,简化js的编写,提高js运行效率
组件
1)理解:用来实现局部功能效果的代码和资源的集合(img、css、html)
2)作用:复用代码,简化项目编码,提高运行效率
模块化
当应用的js都以模块来编写,这个应用就是一个模块化的应用
组件化
当应用是以多组件的方式实现,这个应用就是一个组件化的应用
函数式组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>函数式组件</title>
</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"> //此处一定要写babel
*//1、创建函数式组件*
function MyComponent(){
console.log(*this*)*//此处的this是undefined,因为babel开启了严格模式*
return (<h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>)
}
*//2、渲染组件到页面*
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
*/**
*执行了ReactDOM.render(,document.getElementById('test'))之后发生了什么*
*1、React解析组件标签,找到组件标签*
*2、发现组件时使用函数定义的,随后调用该函数,将返回的虚拟DOM转换为真实DOM,随后呈现在页面中*
**/*
</script>
</body>
</html>
类式组件
//1、创建类式组件*
class MyComponent extends React.Component{
render(){
*//render是放在哪里的?--类的原型对象上,给实例使用*
*//render中的this是谁?--类的原型对象*
return (<h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>)
}
}
*// 2、渲染组件到页面*
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
*/**
*执行了ReactDOM.render(,document.getElementById('test'))之后发生了什么*
*1、React解析组件标签,找到组件标签*
*2、发现组件是使用类定义的,随后new出该类的实例,并通过该实例调用到原型上的render方法*
*3、将render返回的虚拟DOM转换为真实DOM,随后呈现在页面中*
组件实例三大属性1——state
理解
1)state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
2)组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件)
状态数据不能直接赋值,需要用
setState()
setState()
有同步有异步,基本上都是异步更新,自己定义的DOM事件里setState()
是同步的
1)state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
2)组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件)
*//1、创建组件*
class Weather extends React.Component{
*// 初始化状态*
state= {
isHote:true,
wind:'微风'
}
render(){
const {isHot,wind}=*this*.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热':'凉爽'},{wind}</h1>
}
*// 自定义方法---要用赋值语句+箭头函数*
changeWeather=()=>{
const isHot=*this*.state.isHot
*this*.setState({isHot:!isHot})
}
}
*// 2渲染到也页面*
ReactDOM.render(<Weather/>,document.getElementById('test'))
强烈注意
1)组件中render方法中的this为组件实例对象
2)组件自定义的方法中this为undefined,如何解决?
a。强制绑定this:通过 函数对象的bind()
b。箭头函数
3)状态数据,不能直接修改或者更新,需要通过this.setState({})
组件实例三大属性2——props(只读)
理解
1)每个组件对象都会有props属性
2)组件标签的所有属性都保存在props中
作用
1)通过标签属性从组件外部向组件内部传递变化的数据
2)注意:组件内部不要修改props数据
// 1、创建组件*
class Person extends React.Component{
*// 对标签属性进行类型、必要性的限制*
static propTypes={
name:PropTypes.string.isRequired,*//限制name必传,且为字符串*
gender:PropTypes.string,*//限制gender为字符串*
age:PropTypes.number,*//限制age为数值*
speak:PropTypes.func,*//限制speak为函数*
}
*// 指定标签的默认值*
static defaultProps={
gender:'男',*//gender默认值为男*
age:15*//age默认值为15*
}
render(){
const {name,age,gender}=*this*.props
*// this.props.name='jacak'//此行代码会报错,因为props是只读的*
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age+1}</li>
<li>性别:{gender}</li>
</ul>
)
}
}
const p1={name:'张三',age:12,}
const p2={name:'Lisa',gender:'女'}
*// 渲染组件*
ReactDOM.render(<Person {...p1} speak= {speak}/>,document.getElementById('test1'))
ReactDOM.render(<Person {...p2}/>,document.getElementById('test2'))
function speak(){
console.log('我说话了')
}
编码操作
1)内部读取某个属性值
this.props.name
2)对props中的属性值进行类型限制和必要性限制
使用prop-types库进行限制(需要引入prop-types库)
Person.propTypes = {
name:PropTypes.string.isRequired,
age:PropTypes.number
}
3)扩展属性:将对象的所有属性通过props传递
<Person {...person}/>
4)默认值设置
Person.defaultProps={
age:18,
sex:'男'
}
5)组件类的构造函数
constructor(props){
super(props)
console.log(props)//打印所有属性
}
组件实例三大属性3——refs
refs 是组件实例对象中的属性,它专门用来收集那些打了 ref 标签的 dom 元素
比方说,组件中的 input 添加了一个 ref=“input1”
那么组件实例中的 refs 就 ={input1:input(真实 dom)}
这样就可以通过 this.refs.input1 拿到 input 标签 dom 了
就不需要想原生 js 那样通过添加属性 id,
然后通过 document.getElementById (“id”) 的方式拿
字符串形式ref(最快速,但官网不推荐了)
//1、创建组件
class Demo extends React.Component{
showDate=()=> {
console.log(this.refs)
}
showDate2=()=> {
const{input2}=this.refs
console.log(input2.value)
}
render(){
return (<div>
<input type="text" ref='input1' palceholder='点击按钮提示数据'/>
点击提示左侧数据
<input type="text" ref='input2' onBlur={*this*.showDate} palceholder='失去焦点提示数据'/>
</div>)
}
}
//2、渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
回调函数形式的ref
*//1、创建组件*
class Demo extends React.Component{
showDate=()=>{
console.log(*this*.input1.value)
}
showDate2=()=>{
console.log(*this*.input2.value)
}
render(){
return (
<div>
<input type="text" ref={(currentNode)=>{*this*.input1=currentNode}} palceholder='点击按钮提示数据'/>
<button onClick={*this*.showDate}>点击提示左侧数据</button>
<input type="text" ref={(currentNode)=>{*this*.input2=currentNode}} onBlur={*this*.showDate2} palceholder='失去焦点提示数据'/>
</div>
)
}
}
*//2、渲染组件到页面*
ReactDOM.render(<Demo/>,document.getElementById('test'))
createRef创建ref容器
*//1、创建组件*
class Demo extends React.Component{
*// React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的*
myRef = React.createRef()
myRef2 = React.createRef()
showDate=()=>{
alert(*this*.myRef.current.value)
}
showDate2=()=>{
alert(*this*.myRef2.current.value)
}
render(){
return (
<div>
<input type="text" ref={*this*.myRef} palceholder='点击按钮提示数据'/>
<button onClick={*this*.showDate}>点击提示左侧数据</button>
<input type="text" onBlur={*this*.showDate2} ref={*this*.myRef2} palceholder='失去焦点提示数据'/>
</div>
)
}
}
*//2、渲染组件到页面*
ReactDOM.render(<Demo/>,document.getElementById('test'))
事件处理
(1)通过onXxx属性指定事件处理函数(注意大小写)
a.React使用的是自定义(合成)事件,而不是使用的原生DOM事件————为了更好的兼容性
b.React中的事件是通过事件委托方式处理的(委托费组件最外层的元素)---为了高效
(2) 通过event.target得到发生事件的DOM元素对象----不要过度使用ref
【注意】发生事件对象和操作对象是同一个时候,可以省略ref,通过event调用事件
*//1、创建组件*
class Demo extends React.Component{
*// 创建ref容器*
myRef = React.createRef()
myRef2 = React.createRef()
showDate=()=>{
alert(*this*.myRef.current.value)
}
showDate2=(e)=>{
alert(e.target.value)
}
render(){
return (
<div>
<input type="text" ref={*this*.myRef} palceholder='点击按钮提示数据'/>
<button onClick={*this*.showDate}>点击提示左侧数据</button>
<input type="text" onBlur={*this*.showDate2} palceholder='失去焦点提示数据'/>
</div>
)
}
}
*//2、渲染组件到页面*
ReactDOM.render(<Demo/>,document.getElementById('test'))
收集表单数据
理解
包含表单的组件分类
a.受控组件(建议使用)
class Login extends React.Component{
*// 初始化状态*
state={username:'',password:''}
*// 表单提交回调*
showInfo=(event)=>{
event.preventDefault()*//阻止表单提交*
const {username,password}=*this*.state
alert('用户名:'+username+'密码:'+password)
}
*// 保存用户名信息*
saveUsername=(e)=>{
*this*.setState({username:e.target.value})
}
*// 保存密码信息*
savePassword=(e)=>{
*this*.setState({password:e.target.value})
}
render(){
return(
<form action="http://www.baidu.com" onSubmit={*this*.showInfo}>
<span>用户名:</span>
<input type="text" onChange={*this*.saveUsername} name="username"/>
<span>密码:</span>
<input type="password" onChange={*this*.savePassword} name="password"/>
<button >登录</button>
</form>
)
}
}
*//渲染页面*
ReactDOM.render(<Login/>,document.getElementById('test'))
b.非受控组件
class Login extends React.Component{
showInfo=(event)=>{
event.preventDefault()*//阻止表单提交*
const {username,password}=*this*
alert('用户名:'+username.value+'密码:'+password.value)
}
render(){
return(
<form action="http://www.baidu.com" onSubmit={*this*.showInfo}>
<span>用户名:</span>
<input type="text" ref={currentNode=>*this*.username=currentNode} name="username"/>
<span>密码:</span>
<input type="password" ref={currentNode=>*this*.password=currentNode} name="password"/>
<button >登录</button>
</form>
)
}
}
ReactDOM.render(<Login/>,document.getElementById('test'))
高阶函数柯里化
使用高阶函数柯里化实现
<script type="text/babel"> //此处一定要写babel
/*
高阶函数:如果一个函数符合下面2个规范中的任何一个,那么该函数就是高阶函数
1、若A函数,接受的参数是一个函数,那个A就可以称为高阶函数
2、若A函数,调用的返回值依然是一个函数,那么A就可以称为高阶函数。
常见的高阶函数有:Promise、setTimeout、arr.foreach()等等
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
*/
class Login extends React.Component{
// 初始化状态
state={username:'',password:''}
// 表单提交回调
showInfo=(event)=>{
event.preventDefault()//阻止表单提交
const {username,password}=this.state
alert('用户名:'+username+'密码:'+password)
}
saveInfo=(dataType)=>{
return (event)=>{
this.setState({[dataType]:event.target.value})
}
}
render(){
return(
<form action="http://www.baidu.com" onSubmit={this.showInfo} >
<span>用户名:</span>
<input type="text" onChange={this.saveInfo('username')} name="username"/>
<span>密码:</span>
<input type="password" onChange={this.saveInfo('password')} name="password"/>
<button >登录</button>
</form>
)
}
}
//渲染页面
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
不使用高阶函数柯里化实现
<script type="text/babel"> //此处一定要写babel
class Login extends React.Component{
// 初始化状态
state={username:'',password:''}
// 表单提交回调
showInfo=(event)=>{
event.preventDefault()//阻止表单提交
const {username,password}=this.state
alert('用户名:'+username+'密码:'+password)
}
// 保存表单数据到状态中
saveInfo=(dataType,event)=>{
this.setState({[dataType]:event.target.value})
}
render(){
return(
<form action="http://www.baidu.com" onSubmit={this.showInfo} >
<span>用户名:</span>
<input type="text" onChange={(event)=>{this.saveInfo('username',event)}} name="username"/>
<span>密码:</span>
<input type="password" onChange={(event)=>{this.saveInfo('password',event)}} name="password"/>
<button >登录</button>
</form>
)
}
}
//渲染页面
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
组件生命周期
理解
1、组件从创建到死亡他会经历一些特定的阶段
2、React组件中包含一系列钩子函数,会在特定的时刻调用
3、我们在定义组件时,会在特定的生命周期回调函数中做特定的工作。
生命周期(旧版本)
生命周期分为三个阶段:
1、初始化阶段:由ReactDOM.render()触发---初次渲染
1、constructor()
2、componentWillMount()
3、render()
4、componentDidMount()======>常用,
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2、更新阶段:由组件内部this.setState()或父组件重新render触发
1、shouldComponentUpdate()
2、componentWillUpdate()
3、render()======>必须使用一个render
4、componentDidUpdate()
3、卸载组件:由ReactDOM.unmountComponentAtNode()触发
1、componentWillUnmount()======>常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消请阅、
生命周期(新)
重要的钩子
1、render:初始化渲染或更新渲染调用
2、componentDidMount:开启监听,发送ajax请求
3、componentWillUnmount:做一些收尾工作,如:清理定时器
虚拟DOM
本质上其实就是一个 object 对象;
虚拟 dom 上的属性比较少,真实 dom 属性多,因为虚拟 dom 只在 react 内部使用,用不到那么多的属性
虚拟 dom 最终会被 react 转换成真实 dom,呈现再页面上
大致过程:state数据
jsx模版
数据+模版 结合,生成虚拟DOM
(虚拟DOM就是一个JS对象,用它来描述真实的DOM)(损耗了性能)
用虚拟DOM的结构生成真实的DOM来显示
state发生改变
数据+模版 生成新的虚拟DOM(极大提升了性能)
比较原始虚拟DOM和新的虚拟DOM的区别,找差异(极大提升了性能)
直接操作DOM,改变内容
优点:
性能提升
使得跨端应用得以实现
虚拟 DOM 中的 key 的作用:
当状态中的数据发生改变时,react 会根据新数据生成新虚拟 DOM
随后 react 会进行新虚拟 DOM和旧虚拟 DOM的 diff 算法比较
若旧 DOM中找到了与新 DOM相同的 key,则会进一步判断两者的内容是否相同
如果也一样,则直接使用之前的真实 DOM,如果内容不一样,则会生成新的真实 DOM,替换掉原先的真实 DOM
若旧 DOM中没找到与新 DOM相同的 key,则直接生成新的真实 DOM,然后渲染到页面
不用 index 作为 key 的原因:
若对数据进行逆序添加、逆序删除等破坏顺序的操作时会产生不必要的真实 DOM 更新,造成效率低下
如果结构中还包含输入类的 dom,会产生错误 dom 更新,出现界面异常
diff算法
在 react 中如果某个组件的状态发生改变,react 会把此组件以及此组件的所有后代组件重新渲染
不过重新渲染并不代表会全部丢弃上一次的渲染结果,react 还是会通过 diff 去比较两次的虚拟 dom 最后 patch 到真实的 dom 上
diff 算法只比较同一层级,不跨级比较
tag 不相同则直接删掉重建,不再深度比较;tag 和 key 两者都相同,则认为是相同节点,也不再深度比较
虽然如此,如果组件树过大,diff 其实还是会有一部分的开销
因此react 内部通过 fiber 优化 diff 算法,外部建议开发者使用 SCU 和pureComponent