React基础
REACT(面向组件开发)
1.react高效原因
1)使用虚拟DOM技术,不总是直接操作页面真实DOM
2)DOM Diffing算法,最小化界面重绘
2.react使用jsx语法
js创建虚拟DOM太繁琐,所以使用jsx创建虚拟DOM
3.关于虚拟DOM
1.本质是Object类型的对象
2.虚拟DOM比较轻,真实DOM比较重,因为虚拟DOM是React内部在用,无需真实DOM上那么多属性
3.虚拟DOM最终会被react转换为真实DOM,呈现在界面上
DOM:文档对象模型。DOM 为文档提供了结构化表示,并定义了如何通过脚本来访问文档结构。目的其实就是为了能让js操作html元素而制定的一个规范。
4.react引用的js文件
①react.development.js react核心库
②react-dom.development.js 支持react操作DOM
③babel.min.js 将jsx转换为jS
④prop-types.js 用于对组件标签属性进行限制
5.jsx语法规则
①定义虚拟DOM时不要写引号
②标签中混入JS表达式要用{}
③样式的类名不要用class要用className
④内联样式要用style={{key:value}}的形式去写,font-size等要用fontSize小驼峰样式
⑤虚拟标签必须只有一个根标签
⑥标签必须闭合
⑦标签首字母
(1)若小写字母开头,则将标签转换为html中同名元素,若html中无该标签对应的同名元素.则报错
(2)若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错
6.区分jS代码(语句)和jS表达式
①表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
下面这些都是表达式
(1)a
(2)a+b
(3)demo(1)
(4)arr.map()
(5)function test(){}
②语句(代码):
下面这些都是语句(代码):
(1)if(){}
(2)for(){}
(3)switch(){}
7.模块和组件,模块化和组件化
①模块 js文件 作用:复用js,简化js,提高js运行效率
②组件 实现局部功能效果的代码和资源的合集(html/css/js/image等等)
③模块化 当应用的js都以模块来编写的,就是一个模块化的应用
④组件化 当应用以多组件的方式实现,这个应用就是一个组件化的应用
8.函数组件(适用于简单组件[无state的组件]的定义)
//1.创建函数式组件
function MyComponent(){
return <h2>函数组件</h2>
}
//2.渲染组件到界面 (组件首字母大写,标签闭合)
ReactDOM.render(<MyComponent/>,document.getElementById('test'));
react工作顺序:1.解析组件标签,之后寻找MyComponent组件
2.发现组件是使用函数定义的,随后调用该函数,将虚拟DOM转换为真实DOM,随后呈现在界面中.
9.类式组件(适用于复杂组件[有state的组件]的定义)
//1.创建类式组件 必须继承react中的一个类(React.Component)
class MyComponent extends React.Component(){
//必须有render
render(){
//render中的this代表MyComponent的实例对象<=>MyComponent的组件实例对象
//有返回值
return <h2>类式组件</h2>
}
}
//2.渲染组件到界面 (组件首字母大写,标签闭合)
ReactDOM.render(<MyComponent/>,document.getElementById('test'));
react工作顺序:1.解析组件标签,之后寻找MyComponent组件
2.发现组件是使用类定义的,随后new出该类实例,并通过该实例调用原型上的render方法.
3.将render返回的虚拟DOM转换为真实DOM,随后呈现在界面中.
10. 组件实例的三大核心属性
前端解构赋值
const [a,b,c] = [1,2,3]
const {name,age} = {name:'小明',age:18}
10.1 state
理解:1)state是组件对象的重要属性,值必须是对象(可以包含多个key-value的组合)
2)组件被称为'状态机',通过更新组件的state来更新对应界面显示(重新渲染组件)
注意:1)组件render中的this是组件实例对象
2)组件自定义的方法中的this为undefined,如何解决
a.强制绑定this:通过函数对象bind();
b.箭头函数(开发中使用箭头函数)
3)状态数据不能直接修改要使用setState方法
如下方代码
标准写法:
class Weather extrends React.Component{
//构造器 调用1次
constructor(props){
//放到最前方
super(props)
//初始化状态
this.state = {isHot:false,wind:'微风'}
//解决changeWeather中的this指向,并挂在在实例对象上
this.changeWeather = this.changeWeather.bind(this);
}
//调用1+n次,1是初始化的那次,n是状态更新的次数
render(){
//this为组件实例对象(即:Weather)
retrun <h1 onClick={this.changeWeather}>今天天气很{this.state.isHot ? '炎热':'凉爽'}</h1>
}
//点几次调用几次
changeWeather(){
//changeWeather放到了Weather的原型对象上,供实例使用
//由于changeWeather作为onClick的回调,不是通过实例调用,是直接调用
//类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
//获取原来isHot的值
const isHot = this.state.isHot
//严重注意:状态通过setState进行更新,且更新是一种合并不是替换
this.setState({isHot:!isHot})
//严重注意:状态(isHot)不能直接更改
//错误写法this.state.isHot = !isHot;
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'));
精简写法:
//1.创建组件
class Weather extrends React.Component{
//初始化状态
state = {isHot:false,wind:'微风'}
render(){
retrun <h1 onClick={this.changeWeather}>今天天气很{this.state.isHot ? '炎热':'凉爽'}</h1>
}
// 自定义方法----要用赋值语句的形式+箭头函数
// 箭头函数没有自身的this会自己找外层的this
changeWeather = ()=>{
const isHot = this.state.isHot
this.setState({isHot:!isHot})
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'));
10.2 props
理解:1)每个组件都会有props属性
2)组件标签的所有属性都保存在props中
做用:1)通过标签属性从组件外向组件内传递变化的数据
2)注意:组件内部不能修改props数据
如下方代码:
类式组件使用props:
复杂写法:
//1.创建组件
class Person extrends React.Component{
render(){
const {name,age,sex} = this.props
//props是只读的
retrun (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年级:{age+1}</li>
</ul>
)
}
}
//对标签属性进行类型,必要性的限制
Person.proTypes = {
name:ProTypes.string.isRequired, //限制name必传,且为字符窜
sex:ProTypes.string, //限制sex为字符串
age:ProTypes.number, //限制age为数值
speak:ProTypes.func, //限制speak为函数
}
//指定默认标签属性值
Person.defaultProps = {
sex:'男', //性别默认值为男
age:18,//性别默认值为18
}
//2.渲染组件到页面
ReactDOM.render(<Person name="lzq" speak={speak}/>,document.getElementById('test'));
精简写法:
//1.创建组件
class Person extrends React.Component{
//对标签属性进行类型,必要性的限制
static proTypes = {
name:ProTypes.string.isRequired, //限制name必传,且为字符窜
sex:ProTypes.string, //限制sex为字符串
age:ProTypes.number, //限制age为数值
speak:ProTypes.func, //限制speak为函数
}
//指定默认标签属性值
static defaultProps = {
sex:'男', //性别默认值为男
age:18,//性别默认值为18
}
render(){
const {name,age,sex} = this.props
//props是只读的
retrun (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年级:{age+1}</li>
</ul>
)
}
}
//2.渲染组件到页面
ReactDOM.render(<Person name="lzq" speak={speak}/>,document.getElementById('test'));
函数式组件使用props: //函数式组件没有state和refs,proTypes只能写在外侧
//1.创建组件
function Person(props){
const {name,sex,age} = props
retrun (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年级:{age}</li>
</ul>
)
}
//对标签属性进行类型,必要性的限制
Person.proTypes = {
name:ProTypes.string.isRequired, //限制name必传,且为字符窜
sex:ProTypes.string, //限制sex为字符串
age:ProTypes.number, //限制age为数值
speak:ProTypes.func, //限制speak为函数
}
//指定默认标签属性值
Person.defaultProps = {
sex:'男', //性别默认值为男
age:18,//性别默认值为18
}
//2.渲染组件到页面
ReactDOM.render(<Person name="lzq" sex='男' age={18}/>,document.getElementById('test'));
10.3 refs(请勿过度使用refs)
理解:组件内的标签可以定义ref属性来标记自己
10.3.1 字符串形式的ref(不建议使用)
//1.创建组件
class Demo extrends React.Component{
render(){
retrun (
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据">
<button onClick={this.showData1}>点击我提示左侧的数据</button>
<input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据">
</div>
)
}
showData1 = ()=>{
const {input1} = this.refs
alter(input1.value)
}
showData2 = ()=>{
const {input2} = this.refs
alter(input2.value)
}
}
//2.渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'));
10.3.2 回调函数形式的ref
回调函数(1)你定义的函数 (2)你没调用 (3)最后别人调用了
render的return中写js代码{}
//1.创建组件
class Demo extrends React.Component{
showData1 = ()=>{
const {input1} = this
alter(input1.value)
}
showData2 = ()=>{
const {input2} = this
alter(input2.value)
}
saveInput = (c) => {
this.input1 = c
}
render(){
retrun (
<div>
{//注释后面代码:ref回调函数是内联式,在界面更新时执行两次,第一次传null,第二次传DOM元素}
<input ref={currentNode => this.input1 = currentNode} type="text" placeholder="点击按钮提示数据">
{//使用内联式不影响使用,可以使用class类绑定函数的方式避免,以后还是使用内联的比较多}
<input ref={this.saveInput} type="text" placeholder="点击按钮提示数据">
<button onClick={this.showData1}>点击我提示左侧的数据</button>
<input ref={c => this.input2 = c } onBlur={this.showData2} type="text" placeholder="失去焦点提示数据">
</div>
)
}
}
//2.渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'));
10.3.3 createRef (目前react最推荐的形式)
//1.创建组件
class Demo extrends React.Component{
/*
React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点.该容器是'专人专用'的
*/
//创建ref容器
myRef = React.createRef()
myRef2 = React.createRef()
//展示左侧输入框的数据
showData1 = ()=>{
alter(this.myRef.current.value)
}
//展示右侧输入框的数据
showData2 = ()=>{
alter(this.myRef2.current.value)
}
render(){
retrun (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据">
<button onClick={this.showData1}>点击我提示左侧的数据</button>
<input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据">
</div>
)
}
}
//2.渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'));
12. react中的事件处理
(1)通过onXxx属性(如:onClick,onBlur等)指定事件处理函数(注意大小写)
a.React使用的是自定义(合成)事件,而不是原生DOM事件(如:onClick是onclick重新封装的事件)---为了更好的兼容性
b.React中的事件是通过事件委托方式处理的(委托给组件最外层元素) ---为了高效
(2)通过event.target得到发生事件DOM元素对象
当发生事件的DOM元素和操作的DOM元素是一个时候可以省略ref(如下面onBlur事件)
//1.创建组件
class Demo extrends React.Component{
/*
React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点.该容器是'专人专用'的
*/
//创建ref容器
myRef = React.createRef()
//展示左侧输入框的数据
showData1 = ()=>{
alter(this.myRef.current.value)
}
//展示右侧输入框的数据
showData2 = (event)=>{
alter(event.target.value)
}
render(){
retrun (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据">
<button onClick={this.showData1}>点击我提示左侧的数据</button>
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据">
</div>
)
}
}
//2.渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'));
13.收集表单数据
13.1 非受控组件(页面内所有输入类DOM现用现取)
//1.创建组件
class Login extrends React.Component{
handleSubmit = (event) => {
event.preventDefault() //阻止表单提交
const {username,password} = this
alter(`你输入的用户名是:$(username.value),你输入的密码是:$(password.value)`)
}
render(){
retrun (
<form onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username = c} type="text" name="username">
密码:<input ref={c => this.password = c} type="password" name="password">
<button>登录</button>
</form>
)
}
}
//2.渲染组件到页面
ReactDOM.render(<Login/>,document.getElementById('test'));
13.2 受控组件(页面内所有输入类的DOM,随着输入将状态维护在状态里面,需要用的时候从state中取出来)
//1.创建组件
class Login extrends 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
alter(`你输入的用户名是:$(username),你输入的密码是:$(password)`)
}
render(){
retrun (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text" name="username">
密码:<input onChange={this.savePassword} type="password" name="password">
<button>登录</button>
</form>
)
}
}
//2.渲染组件到页面
ReactDOM.render(<Login/>,document.getElementById('test'));
14.高阶函数和函数的柯里化
/*高阶函数:如果一个函数符合下面规范中的任何一个,那该函数就是高阶函数
1.若A函数,接收的参数是一个函数,那该函数就可以称为高阶函数
2.若A函数调用的返回值依然是一个函数,,那该函数就可以称为高阶函数
常见高阶函数:Promise、setTimeout、arr.map()等等
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接受参数最后统一处理的函数编码形式
*/
使用高阶函数和函数柯里化的写法:
//1.创建组件
class Login extrends React.Component{
//初始化状态
state = {
username:'',//用户名
password:'',//密码
}
//保存表单数据
saveFormData = (dataType)=>{
return (event) => {
this.setState({[dataType]:event.target.value})
}
}
//表单提交回调
handleSubmit = (event) => {
event.preventDefault() //阻止表单提交
const {username,password} = this.state
alter(`你输入的用户名是:$(username),你输入的密码是:$(password)`)
}
render(){
retrun (
<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>
)
}
}
//2.渲染组件到页面
ReactDOM.render(<Login/>,document.getElementById('test'));
不使用函数柯里化的写法:
//1.创建组件
class Login extrends React.Component{
//初始化状态
state = {
username:'',//用户名
password:'',//密码
}
//保存表单数据
saveFormData = (dataType,event)=>{
this.setState({[dataType]:event.target.value})
}
//表单提交回调
handleSubmit = (event) => {
event.preventDefault() //阻止表单提交
const {username,password} = this.state
alter(`你输入的用户名是:$(username),你输入的密码是:$(password)`)
}
render(){
retrun (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={ event => this.saveFormData('username',event)} type="text" name="username">
密码:<input onChange={ event => this.saveFormData('password',event)} type="password" name="password">
<button>登录</button>
</form>
)
}
}
//2.渲染组件到页面
ReactDOM.render(<Login/>,document.getElementById('test'));
14.react的生命周期
生命周期回调函数 <=> 生命周期钩子函数 <=> 生命周期函数 <=> 生命周期钩子
14.1 生命周期(旧)
14.1.1 初始化阶段(挂载时)
①constructor() //构造器
②componentWillMount() //组件将要挂载
③render() //初始化渲染
④componentDidMount() //组件挂载完毕
14.1.2 更新阶段
setState() 更新
①shouldComponentUpdate() //控制组件更新的阀门
②componentWillUpdate() //组件将要更新
③render() //状态更新之后执行
④componentDidUpdate() //组件更新完毕
forceUpdate() 强制更新
①componentWillUpdate() //组件将要更新
②render() //状态更新之后执行
③componentDidUpdate() //组件更新完毕
父组件重新render触发
①componentWillReceiveProps() //组件将要接受新的props的钩子
②shouldComponentUpdate() //控制组件更新的阀门
③componentWillUpdate() //组件将要更新
④render() //状态更新之后执行
⑤componentDidUpdate() //组件更新完毕
14.1.3 卸载组件 由ReactDOM.unmountComponentAtNode()触发
componentWillUnmount() //组件将要卸载
14.2 生命周期(新)
新的和旧的相比
①新的可能弃用componentWillMount、componentWillUpdate、componentWillReceiveProps
不要使用,如果使用新版本需要在这三个前面加UNSAFE_ 如:UNSAFE_componentWillMount 不是不安全而是乱用容易出现bug
②新的新提出两个getDerivedStateFromProps和getSnapshotBeforeUpdate
getDerivedStateFromProps 当state的值在任何时候都取决于props,使用场景罕见
14.2.1 初始化阶段(挂载时)
①constructor() //构造器
②getDerivedStateFromProps()
③render() //初始化渲染
④componentDidMount() //组件挂载完毕 ----常用
一般在这个钩子中做一些初始化的事情,例如:开启定时器、发送网路请求、订阅消息
14.2.2 更新阶段
setState()和父组件重新render触发
①getDerivedStateFromProps() //衍生
①shouldComponentUpdate() //控制组件更新的阀门
③render() //状态更新之后执行
④getSnapshotBeforeUpdate() //在更新之前获取快照
④componentDidUpdate() //组件更新完毕
forceUpdate() 强制更新
①getDerivedStateFromProps() //衍生
②render() //状态更新之后执行
③getSnapshotBeforeUpdate() //在更新之前获取快照
④componentDidUpdate() //组件更新完毕
14.2.3 卸载组件 由ReactDOM.unmountComponentAtNode()触发
componentWillUnmount() //组件将要卸载 ----常用
一般在这个钩子中做一些收尾的事情,例如:关闭定时器、取消订阅消息
getSnapshotBeforeUpdate使用场景:
//1.创建组件
class NewsList extrends React.Component{
//初始化状态
state = {newsArr:[]}
componentDidMount(){
setInterval(()=>{
//获取原状态
const {newArr} = this.state
//模拟一条新闻
const news = '新闻' + (newArr.length+1)
},1000)
}
getSnapshotBeforeUpdate(){
return this.refs.list.scrollHeight
}
④componentDidUpdate(preProps,preState,height){
this.refs.list.scrollTop += this.refs.list.scrollHeight - height
}
render(){
retrun (
<div className="list" ref="list">
{
this.state.newArr.map((n,index)=>{
return <div key={index} className="news">{n}</div>
})
}
</div>
)
}
}
//2.渲染组件到页面
ReactDOM.render(<NewsList/>,document.getElementById('test'));
15.DOM的diffing算法
15.1 虚拟DOM中key的作用:
1)简单说:key是虚拟DOM的标识,在更新显示时key起着及其重要的作用
2)详细说:当前状态中的数据发生变化时,react会根据[新数据]生成[新的虚拟DOM],随后React进行[新虚拟DOM]与[旧虚拟DOM]的diff比较,比较规则如下:
a.旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1)若虚拟DOM中内容没变,直接使用之前的真是DOM
(2)若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面之前的真实DOM
b.旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随之渲染到界面
15.2 用index作为key可能会引发的问题:
1)若对数据进行:逆序添加、逆序删除等操作:
会产生没有必要的真实DOM更新 ==>界面效果没问题,但效率低了
2)如果结构中包含输入类DOM:
会产生错位DOM更新 ==>界面有问题
3)注意:如果不存在对数据的逆序添加,逆序删除等破坏顺序的操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
15.3 开发中如何选择key?
1)最好使用每条数据的唯一标识作为key,比如id、手机号、省份证号等
2)如果确定只是简单的展示数据,用index也是可以的
16.父子组件之间的通信:
父组件给子组件传递数据:通过props
子组件给父组件传递数据:父组件给子组件传一个函数,当子组件要给父组件传东西的时候,通过props调用一下该函数
17.defaultChecked和checked的区别,defaultChecked只有在第一次指定的时候才起作用,以后不起作用
18.状态在哪里,操作状态的方法就在哪里