中文官网: https://react.docschina.org/
一、虚拟DOM及JSX语法
1、引入
<!-- 准备容器 -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script src="./js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作dom,需先引入核心库 -->
<script src="./js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script src="./js/babel.min.js"></script>
2、使用jsx创建虚拟dom
<script type="text/babel">
//1.创建虚拟dom
const VDOM = <h1>hello,react</h1> //此处不可写引号,因为不是字符串
const VDOM2 = (
<div id="title">
<span>Hello,react</span>
</div>
)
//2.渲染虚拟dom到页面
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
3、使用js创建虚拟dom(创建虚拟dom不方便)
<script type="text/javascript">
//1.创建虚拟dom
const VDOM = React.createElement('h1',{id:'title'},'Hello React')
//2.渲染虚拟dom到页面
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
4、关于虚拟DOM:
1⃣️本质是object类型的对象虚拟DOM身上属性少,真实DOM身上属性多,因为虚拟DOM是react内部在用,不需要那么多属性
2⃣️虚拟DOM最终会被react展现成真实DOM,呈现在页面上
5、JSX语法规则
1、定义虚拟DOM不要用引号
2、标签中混入js表达式要用{}
3、样式的类名指定不要用class,要用className
4、内敛样式,要用style={{key:value}}的形式去写
5、只有一个根标签
6、标签必须闭合
7、标签首字母:
若小写开头,则转为html中同名元素,若无该标签,则报错;
若大写开头,react就去渲染对应的组件,无则报错。
const myId = "123"
const myData = "hello react"
const VDOM = (
<h2 className="title" id={myId} style={{color:'red'}}>
<span>{myData}</span>
</h2>
)
ReactDOM.render(VDOM,document.getElementById('test'))
6、循环
js表达式:能用变量接收的就是表达式,可以放在任何一个需要值的地方
const data = ["angular","react","vue"]
const VDOM = (
<div>
<h1>前端js框架列表</h1>
<ul>
{
data.map((item,index)=>{
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
ReactDOM.render(VDOM,document.getElementById('test'))
二、组件化
1、函数式组件
//创建函数式组件
function Demo(){
console.log(this); //此处this是undefined,因为babel编译后开启了严格模式
return <h2>我是函数定义的组件,适用于简单组件的定义</h2>
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
2、类式组件
(1)类的复习
class Person{
constructor(name,age){ //构造器函数,由实例对象调用,所以指向实例对象
this.name = name
this.age = age
}
speak(){
console.log(`我叫${this.name},我的年龄是${this.age}`);
}
}
//创建student类,继承于person,不使用constructor也可以接收父类自带的name age
class Student extends Person{
constructor(name,age,grade){
super(name,age)
this.grade = grade
}
speak(){ //重写,重写从父类继承的方法,不向原型链继续查找
console.log(`我叫${this.name},我的年龄是${this.age},我是${this.grade}年级`);
}
}
const s1 = new Student('cq',24,'高一')
console.log(s1);
s1.speak()
(2)react 类式组件
class MyComponent extends React.Component {
render() {
//render是放在哪的? ---类的原型对象上,供实例使用。
console.log(this); //render中的this指向? MyComponent组件的实例对象。
return <h2>我是用类定义的组件,用于复杂组件的定义</h2>
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
//执行ReactDOM.render发生了什么?
//1、React解析组件标签,找到了MyComponent组件。
//2、发现组件是使用类定义的,随后new出来该类的实例。并通过该实例调用原型上的render方法。
//3、将render返回的虚拟DOM,转换为真实DOM。
3、state(组件三大核心属性 — 状态)
(1)标准形式
class Weather extends React.Component {
constructor(props){
super(props)
//1、初始化状态
this.state = {
isHot:false,
wind:'微风'
}
//3、
//解决this为undefinded,即解决this指向问题。
//this.changeWeather = this.changeWeather.bind(this)
}
render() { //render调用1+n次,n是状态更新的次数
const {isHot,wind} = this.state
return <h2 onClick={this.changeWeather}>今天天气很{isHot?'炎热':'凉爽'},{wind}。</h2>
}
changeWeather(){
//2、
//changeWeather放在哪? Weather的原型对象上,供实例使用
//由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用的
//类中的方法默认开启了严格模式,所以changeWeather中的this为undefined
console.log(this)
//4、
// this.state.isHot = !isHot //状态不可这样直接更改,要借助一个内部的api
const { isHot } = this.state
this.setState({isHot:!isHot}) //5、状态必须通过setState进行更改,且是一种合并,不是替换
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
(2)简写方式
class Weather extends React.Component {
//初始化状态
state = {
isHot:false,
wind:'微风'
}
render(){
const {isHot,wind} = this.state
return <h2 onClick={this.changeWeather}>今天天气很{isHot?'炎热':'凉爽'},{wind}</h2>
}
//自定义方法--赋值语句+剪头函数
changeWeather = ()=>{
const { isHot } = this.state
this.setState({isHot:!isHot})
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
4、props
(1)简单使用
class Person extends React.Component{
render(){
const {name,age,sex} = this.props
//this.props.name = 'Jack' //此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{age}</li>
<li>年龄:{sex}</li>
</ul>
)
}
}
ReactDOM.render(<Person name="Tom" age="18" sex="女"/>,document.getElementById('test'))
(2)批量传递props
const p = {name:'cq',age:18,sex:'男'}
ReactDOM.render(<Person {...p}/>,document.getElementById('test'))
//此处...p展开对象,是react渲染组件的独有形式,不可在其他地方使用,其他地方为复制一个对象。
(3)类型限制与默认值
<!-- 引入prop-types,用于对组件的标签属性进行限制 -->
<script src="./js/prop-types.js"></script>
Person.propTypes = {
name:PropTypes.string.isRequired, //字符串且必传
age:PropTypes.number,
sex:PropTypes.string,
speak:PropTypes.func
}
Person.defaultProps = {
age:0,
sex:'未知'
}
(4)props简写方式
使用static写在类内,静态方法可以直接被类调用,但不能在类的实例上调用
class Person extends React.Component{
render(){
const {name,age,sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
)
}
static propTypes = {
name:PropTypes.string.isRequired,
age:PropTypes.number,
sex:PropTypes.string,
speak:PropTypes.func
}
static defaultProps = {
age:0,
sex:'未知'
}
}
const p = {name:'cq',age:18,sex:'男'}
ReactDOM.render(<Person {...p}/>,document.getElementById('test'))
(5)函数式组件声明props
function Person(props){
const {name,age,sex} = props
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
)
}
Person.propTypes = {
name:PropTypes.string.isRequired,
age:PropTypes.number,
sex:PropTypes.string
}
Person.defaultProps = {
age:18,
sex:'男'
}
const p = {name:'cq'}
ReactDOM.render(<Person {...p}/>,document.getElementById('test'))
5、refs
(1)字符串形式的ref(不推荐)
class Demo extends React.Component{
render(){
return(
<div>
<input ref="input1" type="text" placeholder="点击按钮提示"/>
<button ref="button1" onClick={this.showData}>点我提示左侧数据</button>
<input ref="input2" type="text" placeholder="失去焦点提示" onBlur={this.showData2}/>
</div>
)
}
showData = ()=>{
const {input1} = this.refs
alert(input1.value)
}
showData2 = ()=>{
const {input2} = this.refs
alert(input2.value)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'))
(2)回调形式的ref
react执行时会自动调用ref中的箭头函数
class Demo extends React.Component{
render(){
return(
<div>
<input ref={currentNode => this.input1 = currentNode} type="text" placeholder="点击按钮提示"/>
<button ref="button1" onClick={this.showData1}>点我提示左侧数据</button>
<input onBlur={this.showData2} ref={currentNode=>this.input2 = currentNode} type="text" placeholder="失去焦点提示"/>
</div>
)
}
showData1 = ()=>{
const { input1 } = this
alert(input1.value)
}
showData2 = ()=>{
const {input2} = this
alert(input2.value)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'))
(3)createRef
class Demo extends React.Component{
//React.createRef()调用后返回一个容器,可以存储被ref标识的节点,该容器是专人专用的
render(){
return(
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示"/>
<button ref="button1" onClick={this.showData1}>点我提示左侧数据</button>
<input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示"/>
</div>
)
}
myRef = React.createRef()
myRef2 = React.createRef()
showData1 = ()=>{
console.log(this.myRef.current.value);
}
showData2 = ()=>{
console.log(this.myRef2.current.value);
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'))
使用react过程中,尽量避免ref的使用,比如操作节点为当前节点,可以通过event获取节点的属性
6、收集表单数据(非受控、受控组件)
(1)非受控组件(现用现取)
class Login extends React.Component{
handleSubmit = (event)=>{
event.preventDefault(); //阻止默认事件
const {username,password} = this;
alert(`用户名:${username.value},密码:${password.value}`);
}
render() {
return(
<form onSubmit={this.handleSubmit}>
用户名:<input ref={c=>this.username = c} type="text" name="username"/>
密码:<input ref={c=>this.password = c} type="text" name="password"/>
<button>登陆</button>
</form>
)
}
}
ReactDOM.render(<Login/>,document.getElementById('test'))
(2)受控组件(随着你的输入维护状态)(推荐)
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="text" name="password"/>
<button>登陆</button>
</form>
)
}
}
ReactDOM.render(<Login/>,document.getElementById('test'))
7、高阶函数__函数柯里化
//高阶函数,如果一个函数满足下面规范的任何一个,就是高阶函数
//1、若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
//2、若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
//常见的高阶函数:Promise、setTimeout、arr.map()等等
//函数的柯里化:通过函数调用,继续返回函数的方式,实现多次接收参数,最后统一处理的函数编码形式
// function sum(a){
// return (b)=>{
// return (c)=>{
// return a+b+c;
// }
// }
// }
// const res = sum(1)(2)(3);
// console.log(res)
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="text" name="password"/>
<button>登陆</button>
</form>
)
}
}
ReactDOM.render(<Login/>,document.getElementById('test'))
非柯里化实现
class Login extends React.Component{
state = { //初始化状态
username:"",
password:""
}
saveFormData = (dataType,e)=>{
this.setState({[dataType]:e.target.value});
}
handleSubmit = (event)=>{
event.preventDefault(); //阻止默认事件
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="text" name="password"/>
<button>登陆</button>
</form>
)
}
}
ReactDOM.render(<Login/>,document.getElementById('test'))
8、生命周期
(1)旧版16
/*
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
1. constructor()
2. componentWillMount()
3. render()
4. componentDidMount() =====> 常用
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2. 更新阶段: 由组件内部this.setSate()或父组件render触发
1. shouldComponentUpdate()
2. componentWillUpdate()
3. render() =====> 必须使用的一个
4. componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1. componentWillUnmount() =====> 常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
*/
//创建组件
class Count extends React.Component{
//构造器
constructor(props){
console.log('Count---constructor');
super(props)
//初始化状态
this.state = {count:0}
}
//加1按钮的回调
add = ()=>{
//获取原状态
const {count} = this.state
//更新状态
this.setState({count:count+1})
}
//卸载组件按钮的回调
death = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//强制更新按钮的回调
force = ()=>{
this.forceUpdate()
}
//组件将要挂载的钩子
componentWillMount(){
console.log('Count---componentWillMount');
}
//组件挂载完毕的钩子
componentDidMount(){
console.log('Count---componentDidMount');
}
//组件将要卸载的钩子
componentWillUnmount(){
console.log('Count---componentWillUnmount');
}
//控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('Count---shouldComponentUpdate');
return true
}
//组件将要更新的钩子
componentWillUpdate(){
console.log('Count---componentWillUpdate');
}
//组件更新完毕的钩子
componentDidUpdate(){
console.log('Count---componentDidUpdate');
}
render(){
console.log('Count---render');
const {count} = this.state
return(
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
//父组件A
class A extends React.Component{
//初始化状态
state = {carName:'奔驰'}
changeCar = ()=>{
this.setState({carName:'奥拓'})
}
render(){
return(
<div>
<div>我是A组件</div>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName}/>
</div>
)
}
}
//子组件B
class B extends React.Component{
//组件将要接收新的props的钩子
componentWillReceiveProps(props){
console.log('B---componentWillReceiveProps',props);
}
//控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('B---shouldComponentUpdate');
return true
}
//组件将要更新的钩子
componentWillUpdate(){
console.log('B---componentWillUpdate');
}
//组件更新完毕的钩子
componentDidUpdate(){
console.log('B---componentDidUpdate');
}
render(){
console.log('B---render');
return(
<div>我是B组件,接收到的车是:{this.props.carName}</div>
)
}
}
//渲染组件
ReactDOM.render(<Count/>,document.getElementById('test'))
(2)新版17
class Count extends React.Component{
/*
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() =====> 常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
*/
//构造器
constructor(props){
console.log('Count---constructor');
super(props)
//初始化状态
this.state = {count:0}
}
//加1按钮的回调
add = ()=>{
//获取原状态
const {count} = this.state
//更新状态
this.setState({count:count+1})
}
//卸载组件按钮的回调
death = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//强制更新按钮的回调
force = ()=>{
this.forceUpdate()
}
//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props,state){
console.log('getDerivedStateFromProps',props,state);
return null
}
//在更新之前获取快照
getSnapshotBeforeUpdate(){
console.log('getSnapshotBeforeUpdate');
return 'atguigu'
}
//组件挂载完毕的钩子
componentDidMount(){
console.log('Count---componentDidMount');
}
//组件将要卸载的钩子
componentWillUnmount(){
console.log('Count---componentWillUnmount');
}
//控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('Count---shouldComponentUpdate');
return true
}
//组件更新完毕的钩子
componentDidUpdate(preProps,preState,snapshotValue){
console.log('Count---componentDidUpdate',preProps,preState,snapshotValue);
}
render(){
console.log('Count---render');
const {count} = this.state
return(
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
//渲染组件
ReactDOM.render(<Count count={199}/>,document.getElementById('test'))
三、react应用(基于react脚手架)
1、起步
全局安装
npm install -g create-react-app
创建项目
create-react-app project-name
启动
npm start
2、react-router-dom
安装
npm install react-router-dom
//or
yarn add react-router-dom
引入
//index.js中
import { BrowserRouter } from 'react-router-dom';
<BrowserRouter> //或<HashRouter>
<App />
</BrowserRouter>
使用
<Link to="/xxxxx">Demo</Link>
//NavLink可以实现路由链接的高亮,通过activeClassName指定样式名,默认active
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" exact component={Home}/>//严格匹配当前路由,无法继续匹配二级路由
//Redirect一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
<Redirect to="/about"/>
</Switch>
封装
//封装
<NavLink activeClassName="active" className="list-group-item" {...this.props}/>
//使用
<MyNavLink to="/about">About</MyNavLink> //About接受时为this.props.children
向路由组件传递参数
1.params参数
路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
接收参数:this.props.match.params
2.search参数
路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
接收参数:this.props.location.search
备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
import qs form 'querystring'
const {id,title} = qs.parse(this.props.location.search.slice(1))
3.state参数
路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
接收参数:this.props.location.state
备注:刷新也可以保留住参数
函数式路由导航
借助this.prosp.history对象上的API对操作路由跳转、前进、后退
-this.prosp.history.push()
-this.prosp.history.replace()
-this.prosp.history.goBack()
-this.prosp.history.goForward()
-this.prosp.history.go()
例子
<button onClick={()=> this.pushShow(msgObj.id,msgObj.title)}>push查看</button>
<button onClick={()=> this.replaceShow(msgObj.id,msgObj.title)}>replace查看</button>
pushShow = (id,title)=>{
//push跳转+携带params参数
// this.props.history.push(`/home/message/detail/${id}/${title}`)
//push跳转+携带search参数
// this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
//push跳转+携带state参数
this.props.history.push(`/home/message/detail`,{id,title})
}
replaceShow = (id,title)=>{
//replace跳转+携带params参数
//this.props.history.replace(`/home/message/detail/${id}/${title}`)
//replace跳转+携带search参数
// this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`)
//replace跳转+携带state参数
this.props.history.replace(`/home/message/detail`,{id,title})
}
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可以用于解决一些路径错误相关的问题。