中文官网: 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.创建虚拟domconst 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.创建虚拟domconst 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,要用className4、内敛样式,要用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 = namethis.age = age}speak(){console.log(`我叫${this.name},我的年龄是${this.age}`);}}//创建student类,继承于person,不使用constructor也可以接收父类自带的name ageclass 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.statereturn <h2 onClick={this.changeWeather}>今天天气很{isHot?'炎热':'凉爽'},{wind}。</h2>}changeWeather(){//2、//changeWeather放在哪? Weather的原型对象上,供实例使用//由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用的//类中的方法默认开启了严格模式,所以changeWeather中的this为undefinedconsole.log(this)//4、// this.state.isHot = !isHot //状态不可这样直接更改,要借助一个内部的apiconst { isHot } = this.statethis.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.statereturn <h2 onClick={this.changeWeather}>今天天气很{isHot?'炎热':'凉爽'},{wind}</h2>}//自定义方法--赋值语句+剪头函数changeWeather = ()=>{const { isHot } = this.statethis.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.propsreturn (<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} = propsreturn (<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.refsalert(input1.value)}showData2 = ()=>{const {input2} = this.refsalert(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 } = thisalert(input1.value)}showData2 = ()=>{const {input2} = thisalert(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.statereturn(<div><h2>当前求和为:{count}</h2><button onClick={this.add}>点我+1</button><button onClick={this.death}>卸载组件</button><button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button></div>)}}//父组件Aclass 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>)}}//子组件Bclass 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. getDerivedStateFromProps3. render()4. componentDidMount() =====> 常用一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发1. getDerivedStateFromProps2. shouldComponentUpdate()3. render()4. getSnapshotBeforeUpdate5. 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,那么可以使用getDerivedStateFromPropsstatic 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.statereturn(<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//oryarn 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.params2.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/testHashRouter的路径包含#,例如:localhost:3000/#/demo/test3.刷新后对路由state参数的影响(1).BrowserRouter没有任何影响,因为state保存在history对象中。(2).HashRouter刷新后会导致路由state参数的丢失!!!4.备注:HashRouter可以用于解决一些路径错误相关的问题。
