目录
1.jsx的语法规则
1.定义虚拟DOM时,不要写引号
2.标签中混入JS表达式时要用{}
3.样式的类名指定不要用class 要用className
4.内联样式,要用style={{key:value}}的形式写
5.只有一个根标签
6.标签必须闭合
7.标签首字母 若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。 若大写字母开头,react就去渲染对应组件,若组件没有定义,则报错
2.组件
1.函数组件
function Demo(){
console.log(this);//此处this是undefined,因为babel编译后开启了严格模式
return <h2>我是函数组件</h2>
}
ReactDOM.render(<Demo/>,document.getElementById('test'))
2.类式组件
class MyComponent extends React.Component {
render(){
// render是放在MyComponent的原型对象上的,供实例使用,this指向MyComponent的实例对象
return <h2>我是类组件</h2>
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
/*
执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
*/
3.state
class MyComponent extends React.Component{
// 构造器调用一次
constructor(props){
super(props)
// 初始化状态
this.state={isHot:true}
// 解决demo中this的指向问题
this.weather=this.demo.bind(this)
//自身没有demo,但是在原型上找到了demo把this(实例)传过去再赋值给this.weather(拿着原型上的demo生成一个新的挂载到实例自身上)
}
// render 调用1+n次 1是初始化那次,n是状态更新的次数
render(){
// 调用的是自身上的weather
return <h1 onClick={this.weather}>今天天气很{this.state.isHot?'炎热':'凉爽'}</h1>
}
// demo在MyComponent的原型对象上,供实例使用
// 由于weather是作为onClick的回调,所以不是通过实例调用的,是直接调用(window),类中的方法默认开启了局部的严格模式,所以demo中的this是undefined
// demo 点几次调用几次
demo(){
console.log(this);
const isHot=this.state.isHot
// this.state.isHot=!isHot 状态(state)不可直接更改,要借助一个内置API更改
this.setState({isHot:!isHot})
// 状态必须通过setState进行更新,且更新是一种合并,不是替换
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
简写
class MyComponent extends React.Component{
state={isHot:true}
render(){
return <h1 onClick={this.demo}>今天天气很{this.state.isHot?'炎热':'凉爽'}</h1>
}
// 赋值语句+箭头函数
demo=()=>{
const isHot=this.state.isHot
this.setState({isHot:!isHot})
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
4.props
class Person extends React.Component{
render(){
const {name,age,sex}=this.props
return(
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
// 对标签属性进行类型,必要性的限制
Person.propTypes={
name:PropTypes.string.isRequired,//限制name必传
sex:PropTypes.string,
age:PropTypes.number,
speak:PropTypes.func
}
// 指定默认标签属性值
Person.defaultProps={
sex:'女',
age:10
}
ReactDOM.render(<Person name="tom" speak={speak}/>,document.getElementById('test'))
// const p={name:'tom',age:18,sex:'女'}
// ReactDOM.render(<Person {...p}/>,document.getElementById('test'))
function speak(){
}
function Person (props){
const {name,age}=props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
</ul>
)
}
ReactDOM.render(<Person name="tom" age={12}/>,document.getElementById('test'))
// const p={name:'tom',age:18,sex:'女'}
// ReactDOM.render(<Person {...p}/>,document.getElementById('test'))
function speak(){
}
5.refs
class MyComponent extends React.Component{
// showdata=()=>{
// const {input1}=this.refs
// alert(input1.value)
// }
// onblur=()=>{
// 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.onblur} type="text" placeholder="失去焦点提示"/>
// </div>
// )
// }
// showdata=()=>{
// const {input1}=this
// alert(input1.value)
// }
// onblur=()=>{
// const {input2}=this
// alert(input2.value)
// }
// render(){
// return(
// <div>
// <input ref={(currentNode)=>{this.input1=currentNode}} type="text" placeholder="点击按钮提示"/>
// <button onClick={this.showdata}>点我提示左侧的数据</button>
// <input ref={currentNode=>this.input2=currentNode} onBlur={this.onblur} type="text" placeholder="失去焦点提示"/>
// </div>
// )
// }
// React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点
myRef=React.createRef()
myRef2=React.createRef()
showdata=()=>{
alert(this.myRef.current.value)
}
// 发生事件的DOM元素和操作的DOM元素都是input输入框,input发生onblur,提示input数据 ref可以省略
onblur=(e)=>{
alert(e.target.value)
}
render(){
return(
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示"/>
<button onClick={this.showdata}>点我提示左侧的数据</button>
<input onBlur={this.onblur} type="text" placeholder="失去焦点提示"/>
</div>
)
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
6.非受控组件
class MyComponent extends React.Component{
handleSubmit=(e)=>{
e.preventDefault()//阻止表单提交
const {username,password}=this
alert(`你输入的用户名是:${username.value},密码是:${password.value}`)
}
render(){
return(
<form action='http://www.atguigu.com' 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>
)
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
7.受控组件
class MyComponent extends React.Component{
state={
username:'',
password:''
}
// 保存到状态中
saveFormData=(dataType,e)=>{
this.setState({[dataType]:e.target.value})
}
handleSubmit=(e)=>{
e.preventDefault()//阻止表单提交
console.log(e);
const {username,password}=this.state
alert(`你输入的用户名是:${username},密码是:${password}`)
}
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={e=>this.saveFormData('username',e)} type="text" name="username"/>
密码:<input onChange={e=>this.saveFormData('password',e)} type="password" name="password"/>
<button>登录</button>
</form>
)
}
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
3.生命周期
1.初始化阶段: 由ReactDOM.render()触发---初次渲染
1.constructor()
2.getDerivedStateFromProps
3.render()
4.componentDidMount() =====> 常用
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2.更新阶段: 由组件内部this.setSate()或父组件重新render触发
1.getDerivedStateFromProps
2.shouldComponentUpdate()
3.render()
4.getSnapshotBeforeUpdate
5.componentDidUpdate()
3.卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1.componentWillUnmount() =====> 常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
class Count extends React.Component{
// 构造器
constructor(props){
console.log('constructor');
super(props)
this.state={count:0}
}
add=()=>{
const {count}=this.state
this.setState({count:count+1})
}
death=()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 强制更新按钮
force=()=>{
this.forceUpdate()
}
static getDerivedStateFromProps(props){
console.log('getDerivedStateFromProps',props);
return null //即 state 的值在任何时候都取决于 props。
}
getSnapshotBeforeUpdate(){
console.log('getSnapshotBeforeUpdate');
return '111'
}
// 组件挂载完
componentDidMount(){
console.log('componentDidMount');
}
// 组件将要卸载
componentWillUnmount(){
console.log('componentWillUnmount');
}
// 控制组件更新的‘阀门’
shouldComponentUpdate(){
console.log('shouldComponentUpdate');
return true
}
// 组件更新完毕
componentDidUpdate(preProps,preState,snapshotValue){
console.log('componentDidUpdate',preProps,preState,snapshotValue);
}
render(){
console.log('render');
return(
<div>
<h2>当前求和{this.state.count}</h2>
<button onClick={this.add}>按钮</button>
<button onClick={this.death}>按钮</button>
<button onClick={this.force}>按钮</button>
</div>
)
}
}
ReactDOM.render(<Count count="199"/>,document.getElementById('test'))
4.Diff算法
经典面试题:
1).react/vue中的key有什么作用?(key的内部原理是什么?)
2).为什么遍历列表时,key最好不要用index?
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,随后渲染到到页面
2.用index作为key可能会引发的问题:
1.若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2.如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
3.注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,
仅用于渲染列表用于展示,使用index作为key是没有问题的。
3.开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果确定只是简单的展示数据,用index也是可以的。
慢动作回放----使用index索引值作为key
初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM:
<li key=0>小张---18<input type="text"/></li>
<li key=1>小李---19<input type="text"/></li>
更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的虚拟DOM:
<li key=0>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
-----------------------------------------------------------------
慢动作回放----使用id唯一标识作为key
初始数据:
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
初始的虚拟DOM:
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
更新后的数据:
{id:3,name:'小王',age:20},
{id:1,name:'小张',age:18},
{id:2,name:'小李',age:19},
更新数据后的虚拟DOM:
<li key=3>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>
5.路由的基本使用
1.明确好界面中的导航区、展示区
2.导航区的a标签改为Link标签 <Link to="/xxxxx">Demo</Link>
3.展示区写Route标签进行路径的匹配<Route path='/xxxx' component={Demo}/>
4.<App>的最外侧包裹了一个<BrowserRouter>或<HashRouter>
1.路由组件与一般组件
1.写法不同:
一般组件:<Demo/>
路由组件:<Route path="/demo" component={Demo}/>
2.存放位置不同:
一般组件:components
路由组件:pages
3.接收到的props不同:
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收到三个固定的属性
history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: "/about"
search: ""
state: undefined
match:
params: {}
path: "/about"
url: "/about"
2.NavLink与封装NavLink
1.NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
2.标签体内容是一个特殊的标签属性
3.通过this.props.children可以获取标签体内容
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
{/* <NavLink className="list-group-item" to="/about">About</NavLink>
<NavLink className="list-group-item" to="/home">Home</NavLink> */}
<MyNavLink to="/about" title="About">About</MyNavLink>
<MyNavLink to="/home" title="Home">Home</MyNavLink>
import React, { Component } from 'react'
import {NavLink} from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
return (
<NavLink className="list-group-item" {...this.props} />
)
}
}
3.Redirect的使用
1.一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
2.具体编码:
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
4.路由传参
1.params传参
render() {
const {messageArr}=this.state
return (
<div>
<ul>
{
messageArr.map((msgObj) => {
return (
<li key={msgObj.id}>
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr />
<Route path="/home/message/detail/:id/:title" component={Detail}/>
</div>
)
}
render() {
const {id,title}=this.props.match.params
const result=detail.find((detailObj)=>{
return detailObj.id===id
})
return (
<ul>
<li>ID:{id}</li>
<li>title:{title}</li>
<li>content:{result.content}</li>
</ul>
)
}
2.search参数
render() {
const {messageArr}=this.state
return (
<div>
<ul>
{
messageArr.map((msgObj) => {
return (
<li key={msgObj.id}>
<Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr />
<Route path="/home/message/detail" component={Detail}/>
</div>
)
}
import React, { Component } from 'react'
import qs from 'qs'
const detail = [
{ id: '01', content: '1' },
{ id: '02', content: '2' },
{ id: '03', content: '3' },
]
export default class Detail extends Component {
render() {
const {search}=this.props.location
const {id,title}=qs.parse(search.slice(1))
const result=detail.find((detailObj)=>{
return detailObj.id===id
})
return (
<ul>
<li>ID:{id}</li>
<li>title:{title}</li>
<li>content:{result.content}</li>
</ul>
)
}
}
3.state参数
render() {
const {messageArr}=this.state
return (
<div>
<ul>
{
messageArr.map((msgObj) => {
return (
<li key={msgObj.id}>
<Link to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}</Link>
</li>
)
})
}
</ul>
<hr />
<Route path="/home/message/detail" component={Detail}/>
</div>
)
}
render() {
const {id,title}=this.props.location.state
const result=detail.find((detailObj)=>{
return detailObj.id===id
})
return (
<ul>
<li>ID:{id}</li>
<li>title:{title}</li>
<li>content:{result.content}</li>
</ul>
)
}
5.编程式路由
import React, { Component } from 'react'
import Detail from './Detail'
import { Link, Route } from 'react-router-dom'
export default class Message extends Component {
state = {
messageArr: [
{ id: '01', title: '消息1' },
{ id: '02', title: '消息2' },
{ id: '03', title: '消息3' },
]
}
replaceShow=(id,title)=>{
this.props.history.replace(`/home/message/detail/${id}/${title}`)
}
pushShow=(id,title)=>{
this.props.history.push(`/home/message/detail/${id}/${title}`)
}
forward=()=>{
this.props.history.goForward()
}
back=()=>{
this.props.history.goBack()
}
render() {
const { messageArr } = this.state
return (
<div>
<ul>
{
messageArr.map((msgObj) => {
return (
<li key={msgObj.id}>
<Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link>
<button onClick={()=>this.pushShow(msgObj.id,msgObj.title)}>push</button>
<button onClick={()=>this.replaceShow(msgObj.id,msgObj.title)}>replace</button>
</li>
)
})
}
</ul>
<hr />
<Route path="/home/message/detail/:id/:title" component={Detail} />
<button onClick={this.forward}>前进</button>
<button onClick={this.back}>后退</button>
</div>
)
}
}
6.BrowserRouter与HashRouter的区别
1.底层原理不一样:
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
HashRouter使用的是URL的哈希值。
2.path表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
HashRouter的路径包含#,例如:localhost:3000/#/demo/test
3.刷新后对路由state参数的影响
(1).BrowserRouter没有任何影响,因为state保存在history对象中。
(2).HashRouter刷新后会导致路由state参数的丢失!!!
4.备注:HashRouter可以用于解决一些路径错误相关的问题。