就如同看书一样,一千个人眼里,有一千个哈姆雷特,每个人的理解都有不同,想要找到作者最为准确的意思,还是要到官网站查询
官网:React 官方中文文档 – 用于构建用户界面的 JavaScript 库
目录
五、经典面试题:React中this指向丢失问题 及 解决方案
十、React基于浏览器项目的路由导航模块:react-router-dom
净美仕PC端官网项目全流程开发架构
一、React概述
英文官网:https://reactjs.org/
中文网站:https://zh-hans.reactjs.org/
用于构建用户界面的 JavaScript 库 —— React只涉及到JS,不涉及HTML/CSS;
特点1:声明式(数据响应式) —— MVVM框架典型特征
特点2:组件化
特点3:一次学习,到处使用 —— 适用于网站、H5、WebApp、原生App
由Facebook/Meta在2013年5月开源,目前最新稳定版是v18.2。
项目中使用React的两种方式:
方式1:脚本引入式 —— 在页面中用<script src="react.js | react-dom.js | babel.js"> |
方式2:脚手架方式 —— 在开发端使用Node.js/Webpack/开发服务器环境运行/打包React应用 ①安装正确版本的Node.js node -v 必须大于等于V14.0 ②下载全局脚手架工具,并运行它,创建一个新的项目(项目名称不能包含汉字/大写字母) npx create-react-app 项目名 ④进入项目目录,启动其中自带的开发服务器 cd 项目名 npm start |
二、JSX
JSX:JavaScript XML,使用XML语法创建JS对象的技术。这种语法本身是TypeScript的一部分。
JSX在React中不是必需的;
如果使用了,同时还需要引入babel.js编译器;
所有的JSX都将被编译为:React.createElement( )
语法要求如下:
①JSX语法是严格的“XML语法”:标签必须结束、属性值必须有引号、标签名/属性名区分大小写;
②一段JSX中可以添加任意换行;
③一段JSX中有且只能有一个父元素;如果不想引入无用的父元素,可以使用:
<></> 或 <React.Fragment></React.Fragment>
④JSX中的注释写作: {/* 注释内容 */}
⑤JSX中元素的属性不等同于HTML标签的属性,例如:class写作className、value写作defaultValue、onclick写作onClick、onmouseover写作onMouseOver....
⑥JSX中事件处理函数中的this不指向“事件源对象”(不是真实DOM元素)、也不指向window,而指向undefined(事件处理函数是运行在“严格模式”下的全局函数——Babel编译后的结果都是运行在“严格模式”)
⑦JSX要求:HTML标签对应的组件名必须用纯小写形式;自定义组件名必须用“大驼峰命名法”
⑧JSX中的所有的style都不能赋值为字符串——及时原生DOM中style也不是字符串!而是对象!
三、React中的数据绑定
①内容绑定
<span>{表达式}</span>
②属性绑定
<img src={表达式}/>
③样式绑定
<div style={表达对象} className={表达式}></div>
④事件绑定
<button onClick={表达式}></button>
形式1:处理函数不需要传参 onClick={f1}
形式2:处理函数需要传参 onClick={()=>f2(实参)}
⑤双向数据绑定
Vue.js中使用v-model指令实现双向数据绑定;React中没有指令的概念,只能模拟实现:
Model=>View: <input value={uname}/>
View=>Model: <input onChange={e=>setUname( e.target.value )}/>
⑥条件渲染 —— React中没有“指令”的概念
类似于Vue.js中的v-show指令——修改display样式:
<div style={display: this.state.show?'block':'none'}>
类似于Vue.js中的v-if指令——{}中只能出现表达式,if不是:
{ this.state.show && JSX } if...
{ this.state.show ? JSX1 : JSX2 } if...else...
⑦列表渲染
React中没有指令的概念,没有v-for或类似的语法!
{ arr.map( (item,index)=>(JSX) ) }
技术 |
innerText绑定 |
innerHTML绑定 |
原生JS |
e.innerText = '内容' |
e.innerHTML = '内容' |
Vue.js |
<div>{ {内容}}</div> <div v-text="内容"></div> |
<div v-html="内容"></div> |
小程序/uni-app |
<text>{ {内容}}</text> |
<rich-text nodes="内容"/> |
React |
<div>{内容}</div> |
<div dangerouslySetInnerHTML={ {__html: "内容"}}></div> |
React中的“单一数据源”和“受控组件”的概念: 在React中,一个输入框或类似的表单字段,只允许存在一个数据源:要么是键盘输入,要么是模型变量;即: 没有指定value属性,那么用户可以使用键盘随意输入——数据源就是键盘输入; 如果指定value属性,那么只有value属性绑定的模型变量改变了输入框内容才能改变,键盘无法输入了——数据源就是模型变量;此时,输入框被模型变量“控制”,称为“受控组件”。 |
|
“受控组件”和“非受控组件”—— 面试题 受控组件:程序需要监控用户的每一次输入,确保每次输入都是合法的 —— onChange 非受控组件:程序无需监控用户的每次输入,只需最后提交的时候可以读取到最终的内容即可 |
|
//受控组件 —— 适用于需要监控每次输入 let [kw, setKw] = useState("") let doKwChange = (e)=>{ setKw(e.target.value) } <input value={kw} onChange={doKwChange}/> |
//非受控组件 —— 无需监控用户的每次输入 let kwInput = useRef( ) doSubmit(){ let v = kwInput.current.value } <input ref={kwInput} defaultValue="默认值"/> |
面向过程(Procedure)的编程思想:把任务分为多个步骤,每个步骤用一个函数来实现 |
面向对象(Object)的编程思想:把任务涉及到的数据/方法划分为不同的角色,角色之间互相发消息 |
四、React中自定义组件
React中提供了两种创建自定义组件的语法:
语法1:函数式组件(体现的是面向过程的编程思想——类似于C语言)
function XzRating( props ){ //创建子组件
return JSX
}
let vchild = <XzRating/> //使用子组件
语法2:类式组件(体现的是面向对象的编程思想——类似于Java语言)
class XzRating extends React.Component{ //创建子组件
render(){ return JSX }
}
let vchild = <XzRating/> //使用子组件
五、经典面试题:React中this指向丢失问题 及 解决方案
this指向undefined问题出现在:
① class组件中 ②是事件处理函数中
问题分析:
1)this不会指向事件源对象 —— JSX=>React.createElement()=>创建的都是虚拟DOM对象
2)this也不指向window —— JSX=>"use strict",严格模式下函数中的this不指向window
3)严格模式下,全局函数中的this永远指向undefined
解决方案:
方案1:箭头函数(无法传参) f1 = ()=>{ log(this) } onClick = {this.f1} |
方案2:箭头函数(可以传参) f1(形参){ log(this) } onClick = {()=>this.f1(实参)} |
方案3:使用bind(函数多次使用就要多次bind,从而生成多个副本) f1(){ log(this) } onClick = {this.f1.bind(this)} |
方案4:使用bind constructor(){ super() this.f1 = this.f1.bind( this ) } f1(){ log(this) } onClick = {this.f1} |
五一、组件的状态数据(State)
Vue.js中,组件的状态数据是“Push-Based”——数据的改变会自动推送出去,告诉渲染系统重新渲染;
微信小程序中,组件的状态数据是“Pull-Based”——程序必须明确的告诉渲染系统组件数据被改变了,请重新渲染;
React中,组件数据采用“Pull-Based”,即组件的状态数据改变必须明确的通知渲染系统。
声明组件的状态数据:
state = { //类似于Vue.js中的data
uname: "yaya",
upwd: "123456",
products: [...],
}
修改组件的状态数据:
this.state.uname = 'yadan' //不会在页面中重新渲染
this.setState({ uname: 'yadan' }) //修改状态变量的同时通知渲染系统
提示:
①setState方法如果仅修改了state中的部分数据,其它数据会自动合并进来(不会丢失)
②setState方法因为既要修改状态数据,又要通知渲染系统修改真实DOM树,比较耗时间,此方法对数据的修改是“异步的”
③如果想查看异步修改后的状态数据,只能使用setState方法的第二个参数:“异步修改后的回调函数”
this.setState( state, ()=>{ 此函数在数据修改后+组件重新渲染后执行 } )
六、React中的父子组件见传参
父组件 => 子组件 —— “Props Down”
//Parent state = {age:18} <Child childAge={this.state.age}/> |
//Child <div>{this.props.childAge}</div> |
//Parent let [age] = useState(18) <Child childAge={age}/> |
//Child <div>{props.childAge}</div> |
子组件 => 父组件 —— “Props Up”(不是Events Up)
//Parent let doNum = function( num ){ log(num) } //父组件有个方法需要num,但是自己却没有该数据 <Child f1={doNum}/> //child.f1 = parent.doNum |
//Child let n = 3 //要显示的页号 props.f1( n ) //子组件调用父组件传递来的方法,并将自己的数据作为实参 |
七、class组件的生命周期方法
阶段1:挂载阶段
① constructor( ):组件对象被创建了
② render( ):渲染组件内容
③ componentDidMount( ):组件完成挂载,类似于mounted/onLoad did-do的完成时
阶段2:更新阶段(props或state改变)
④ shouldComponentUpdate( newProps, newState ): 此次更新应该重新渲染到视图吗?默认返回true
② render( ):渲染组件内容
⑥ componentDidUpdate( ):组件完成更新,类似于updated
阶段3:卸载阶段
⑦ componentWillUnmount( ):组件即将卸载,类似于beforeMount
八、重点/难点:Hook
自从React诞生,“函数式组件”功能一直有缺失:没有状态、没有生命周期方法 —— 没有父类,导致函数式组件只能编写一些简单的固定内容的组件。
V16.8开始,官方为“函数式组件”弥补了不足——增加了“Hook”的概念,同时由于自身的优势(天然没有this),导致越来越多的程序员开始偏向使用“函数式组件”。
当前的项目中,可以同时使用“类式组件”和“函数式组件”。
Hook:钩子,是官方为“函数式组件”增加的新特性,用于为函数式组件“钩住”一些扩展功能。
Hook有如下特点:
①只用于“函数式组件”;不用与“类式组件”;
②所有的钩子本质都是一个函数;
③官方要求,钩子函数都必须以“use”卡头,形如:useXxxx( );
④官方要求,钩子函数只能在函数式组件内部最顶层调用,不能在内层调用;
提示:React官方目前提供了15钩子函数,第三方模块也会提供的更多的钩子
function Rating(props){ useXxx( ) //正确语法 if(){ useXxx( ) } //错误写法 for(){ useXxx( ) } //错误写法 function f1(){ useXxx( ) } //错误写法 } |
示例1:为函数式组件增加“状态变量”—— useState
import {useState} from 'react'
let [ 变量名, 修改状态变量的异步方法 ] = useState(状态变量的初始值)
注意:使用useState()创建的修改方法都是异步执行的;但是与setState(state, 回调函数)不同,此处没有第二个回调函数参数!
示例2:为函数式组件增加“生命周期方法/辅助功能/副作用(SideEffect)”—— useEffect —— 难点
注意:Hook创建的生命周期方法最特别之处是:都是匿名函数,且有6个!!!
//生命周期方法1:任意数据发生改变(从无到有 + 从1到2) useEffect( ()=>{ //此方法 = 组件挂载 + 任意数据发生改变 } ) //没有依赖列表 |
//生命周期方法2:指定数据发生改变(从无到有 + 从1到2) useEffect( ()=>{ //此方法 = 组件挂载 + 指定数据发生改变 } , [指定数据名, ...] ) //指定了依赖列表,且其中有数据 |
//生命周期方法3:组件挂载(数据从无到有) useEffect( ()=>{ //此方法 = 组件挂载 } , [ ] ) //指定了依赖列表,但其中没有数据 |
//生命周期方法4:任意数据发生改变直到消亡(从1到2 + 从有到无) useEffect( ()=>{ return ()=>{ //此方法 = 任意数据发生改变 + 组件即将卸载 } } ) //没有依赖列表 |
//生命周期方法5:指定数据发生改变直到消亡(从1到2 + 从有到无) useEffect( ()=>{ return ()=>{ //此方法 = 任意数据发生改变 + 组件即将卸载 } } , [指定数据名, ...]) //指定了依赖列表,且其中有数据 |
//生命周期方法6:组件卸载(从有到无) useEffect( ()=>{ return ()=>{ //此方法 = 组件即将卸载 } } , [ ]) //指定了依赖列表,但其中没有数据 |
九、不同架构的路由导航模块:
Vue.js: vue-router 小程序/uni-app: pages.json React(V18.2): 基于浏览器的项目:react-router-dom(V6.4) 脱离浏览器的项目:react-navigation |
router:路由器,类似于马路边的“百事通老大爷” routes:很多路由,路由列表,路由词典 route:路由,路径经由,经由一条路径可以到达期望的目的地 路径地址 <=> 目标对象 |
十、React基于浏览器项目的路由导航模块:react-router-dom
英文官网:https://reactrouter.com
没有中文官网!国内的翻译网站目前都是过时的!!
使用步骤:
①安装必需的模块文件
npm i react-router-dom
②创建一个路由器对象(Router),其中包含一个路由词典(Routes),其中包含多条路由(Route)
const router = createBrowserRouter( [
{ path: '地址', element: <组件/> },
......
] )
const root = ReactDOM.createRoot(....)
root.render(<RouterProvider router={router}/>)
③使用浏览器测试每个路由地址
④如何进行页面跳转:
模板法:<Link to="">跳转</Link>
脚本法:import {useNavigate} from 'react-router-dom'
let nav = useNavigate( ) //使用“导航”钩子
nav('/地址') //执行跳转
⑤如何获取当前页面所在地址
let {pathname, seach} = useLocation( )
⑥如何进行跳转传参
页面1:<Link to="/页面2?k1=v1&k2=v2...">跳转到页面2</Link>
页面2:let [params] = useSearchParams( )
let v2 = params.get( 'k2' )
小知识:根据HTTP协议的规定,URL地址后面的“?k=v&k=v”称为: QueryString:查询字符串 或者 SearchParams:搜索参数 |
实现AJAX效果可用的技术: ①原生XHR ②jQuery.ajax() ③axios() ④wx.request()/uni.request() ⑤原生fetch |
十一、React中异步请求服务器端接口
基于浏览器的React项目,可以使用①③⑤三种方案之一进行服务器端接口异步访问。
Fetch API:抓取/获取,是W3C委员会提供的H5新标准技术,用于取代XHR;目前除了老IE浏览器其它浏览器都原生支持。
使用手册:https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch
//使用fetch发起GET请求 let url = "" let res = await fetch( url ) let data = await res.json( ) //data就是响应消息主体解析后的JS数据 |
//使用fetch发起POST请求 let url = "" let options = { method: 'POST', headers:{'Content-Type': '....'}, body: 'k1=v1&k2=v2' } let res = await fetch( url, options ) let data = await res.json( ) //data就是响应消息主体解析后的JS数据 |
常见框架 |
常用的组件库 |
Vue.js |
ElementUI、MintUI、VantUI... |
小程序 |
WeUI、Vant、Taro... |
uni-app |
uni-ui扩展组件库 |
React |
AntDesign、TDesign、Vant、ReactNative... |
十二、ReactNative组件库
ReactNative是一个基于React框架的UI组件库,只用于移动端NativeApp,性能远超WebApp;RN组件库是业界最最特别的一个UI组件库!!!
运行原理:RN代码(纯JS) => Webpack编译 => 得到JAVA/OC代码 => JAVA/OC编译 => .apk/.ipa => 安装到手机并运行。
RN的魅力:让程序员不写JAVA或OC就能编写出NativeApp!!! |
官网:http://reactnative.dev/ 最新版:0.70
中文网:https://www.reactnative.cn/
在本机搭建RN项目开发运行环境 —— 3GB+(可能会下载失败)
①安装必需的编译软件:
Node.js:Webpack需要的
JDK:Java编译需要的
②安装Android原生App开发软件
③使用Android开发软件下载编译必需的第三方模块
④下载并安装RN项目脚手架工具,创建空白项目
⑤编译空白项目,得到AndroidApp安装文件
⑥把App安装文件安装到真实Android手机或夜神模拟器
提示:上述过程安装的软件有些只能放到 c:/users/xxx ,要求当前登录用户必须是纯英语用户名,而且权限必须管理员权限 —— 如果不是,百度搜“Windows10启用administrator账户” |