原生JavaScript的痛点
-
原生JavaScript操作DOM 繁琐、效率低(DOM-API 操作 UI)
-
使用JavaScript直接操作DOM,浏览器会进行大量的重绘重排
-
原生JavaScript没有组件化编码方案,代码复用率低
React的特点
- 采用组件化模式、声明式编码,提高开发效率及组件复用率。
- 在React Native种可以使用React语法进行移动端开发
- 使用虚拟DOM+优秀的Diffing 算法,尽量减少与真实DOM的交互
关于虚拟DOM
- 本质是Object类型的对象(一般对象)
- 虚拟DOM比较“轻” 真实DOM比较“重”,因为虚拟DOM是React 内部再用,无需真实DOM上那么多的属性
- 虚拟DOM最终会被React转化为真实DOM 呈现再页面上
babel=>
- es5=>es6
- jsx=>js
JSX
- 全称:JavaScript XML
- react 定义的一种类似于XML的JS 扩展语法:JS+XML
- 本质是React.createElement(component,props,...children)方法的语法糖
- 作用:用来简化创建虚拟DOM
- 写法:var ele = <h 1>Hello JSX< /h1 >
- 注意1:它不是字符串,也不是HTML/XML标签
- 注意2:它最终产生的就是一个JS对象
- 标签名任意:HTML标签或其他标签
JSX语法规则:
-
定义虚拟DOM时,不要写引号
-
标签中混入JS表达式时要用{}
注意区分【js语句(代码)】与【js表达式】
1.表达式:一个表达式会产生一个值,可以放在任何一个需要值得地方 下面这些都是表达式: (1).a (2).a+b (3).demo(1) (4).att.map() (5).function test() {} 2.语句(代码) 下面这些都是语句(代码) (1).if(){} (2).for(){} (3).switch(){case:xxxx}
-
样式的类名指定不要用class,要用className
-
内联样式 要用style={{kry:value}}的形式去写
-
虚拟DOM必须只有一个根标签
-
标签必须闭合
-
标签首字母
- 若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。
- 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
模块与组件、模块化与组件化的理解
- 模块(主要是拆分js)
- 理解:向外提供特定功能的js程序,一般iu是一个js文件
- 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂
- 作用:复用js,简化js的编写,提高js运行效率
- 组件
- 理解:用来实现局部功能效果的代码和资源的集合(html/css/音视频等)
- 为什么:一个界面的功能更复杂
- 作用:复用编码,简化项目编码,提高运行效率
- 模块化 应用的js都是以模块来编写
- 组件化 应用是以多组件的方式实现
函数式组件
执行了ReactDOM.render(< MyComponent/ >,document.getElementById('test'))之后,发生了什么
- React解析组件标签,找到了MyComponent组件
- 发现组件时使用函数定义的,随后调用该函数,将返回的虚拟DOM转化为真实DOM,随后呈现在页面中
类式组件
类的基本
- 类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加指定属性时采写
- 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的
- 类中所定义的方法,都放在了类的原型对象上,供实例去调用。
执行了ReactDOM.render(< MyComponent/ >,document.getElementById('test'))之后,发生了什么
- React解析组件标签,找到了MyComponent组件
- 发现组件时使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法
- 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中
组件实例的三大核心属性1:state
- 理解
- state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
- 组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件)
- 强烈注意
-
组件中render方法中的this为组件实例对象
-
组件自定义的方法中this为undefined,如何解决?
a. 强制绑定this:通过函数对象的bind()
b. 箭头函数
-
状态数据,不能直接修改或更新
组件实例的三大核心属性2:props
- 理解
- 每个组件对象都会有props(properties的简写)属性
- 组件标签的所以属性都保存再props中
- 作用
- 通过标签属性从组件外向组件内传递变化的数据
- 注意:组件内部不要修改props数据
- 编码操作
-
内部读取某个属性值
this.props.name
-
对props中的属性值进行类型限制和必要性限制
第一种方式(React v15.5开始已弃用)
Person.propTypes = {
name:React.PropTypes.string.isRequired,//限制name必传,且为字符串
sex:React.PropTypes.string,//限制sex为字符串
age:React.PropTypes.number,//限制age为数值
}
第二种方式 使用prop-types库进行限制(需要引用prop-types库)
Person.propTypes = {
name:PropTypes.string.isRequired,//限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
3. 扩展属性:将对象的所以属性通过props传递
<Person {...person}/>
4. 默认属性值
Person.defaultProps={
sex:"不男不女",//sex默认值为不男不女
age:18//age默认值为18
}
5. 组件类的构造函数
constructor(props){
super(props)
console.log(props)
}
组件实例的三大核心属性3:ref
组件内的标签可以定义ref属性来标识自己
-
字符串形式的ref
<input ref='input1' .../>
-
回调形式的ref
<input ref={(c)={this.input1= c}}
-
createRef创建ref容器
myRef = React.createRef() <input ref = {this.myRef}/>
事件处理
- 通过onXxx属性指定事件处理函数(注意大小写)
- React使用的是自定义(合成)事件,而不是使用的原生DOM事件
- React 种的事件是通过事件委托方式处理的(委托给组件最外层的元素)
- 通过event.target 得到发生事件的DOM 元素对象
收集表单数据 包含表单的组件分类
- 受控组件
- 非受控组件
组件的生命周期
- 组件从创建到死亡它会经历一些特定的阶段
- React组件中包含一系列钩子函数(生命周期回调函数),会在特定的时刻调用
- 我们在定义组件时,会在特定的生命周期回调函数中,作特定的工作
- 生命周期原理图(旧)
-
初始化阶段: 由ReactDOM.render()触发---初次渲染
- constructor()
- componentWillMount()
- render()
- componentDidMount()
-
更新阶段: 由组件内部this.setSate()或父组件重新render触发
- shouldComponentUpdate()
- componentWillUpdate()
- render()
- componentDidUpdate()
-
卸载组件: 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()
- 生命周期原理图(新)
- 初始化阶段: 由ReactDOM.render()触发---初次渲染
- constructor()
- getDerivedStateFromProps
- render()
- componentDidMount()
- 更新阶段: 由组件内部this.setSate()或父组件重新render触发
- getDerivedStateFromProps
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate
- componentDidUpdate()
- 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()
React 应用
功能界面的组件化编码流程(通用)
- 拆分组件:拆分界面,抽取组件
- 实现静态组件:使用组件实现静态页面效果
- 实现动态组件:
- 动态显示初始化数据
- 数据类型
- 数据名称
- 保存在那个组件?
- 交互(从绑定事件监听开始)
todoList 案例相关知识点
- 拆分组件、实现静态组件,注意:className、style得写法
- 动态初始化列表,如何确定将数据放在哪个组件得state中?
- 某个组件使用:放在自身得state中
- 某些组件使用:放在他们共同得父组件state中(官方称此操作为:状态提升)
- 关于父子之间通信
- 【父组件】给【子组件】传递数据:通过props传递
- 【子组件】给【父组件】传递数据:通过props传递,要求父组件提前给子组件传递一个函数
- 注意defaultChecked 和checked 的区别,类似的还有LdefaultValue 和 value
- 状态在哪里,操作状态的方法就在哪里
github搜索案例相关知识点
-
设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办
-
ES6小知识点: 解构赋值+重命名
let obj = {a:{b:1}} const {a} = obj//传统解构赋值 const {a:{b}} = obj //连续解构赋值 const {a:{b:value}} = obj //连续解构赋值+重命名
-
消息订阅与发布机制
- 先订阅,再发布(理解:有一种隔空对话的感觉)
- 适用于任意组件间通信
- 要在组建的compontWithUnmount中取消订阅
-
fetch发送请求(关注分离的设计思想)
try{ const response = await fetch(`/api1/search/users?q=${keyWord}`) const data = await respnse.json() console.log(data) }catch(error){ console.log('请求出错',error) }
React 路由
- SPA的理解
- 单页Web应用(single page web applicat,SPA)
- 整个应用只有一个完整的页面
- 点击页面中的链接 不会刷新页面,只会做页面的局部更新
- 数据都需要通过ajax请求获取,并在前端异步展现
- 路由的理解
- 一个路由就是一个映射关系(key:value)
- key 为路径,value可能是function 或 component
- 路由分类
- 后端路由
- 理解:value 是 function,用来处理客户端提交的请求
- 注册路由:router.get(path,function(req,res))
- 工作过程: 当node 接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据
- 前端路由
- 浏览器端路由,value时component,用于展示页面内容
- 注册路由:
- 工作过程:当浏览器的path变为/test时,当前路由组件就会变为Test组件
路由的基本使用
-
明确好界面中的导航区、展示区
-
导航区的a标签改为Link标签
< Link to="/xxxx">demo</ Link>
-
展示区写Router标签进行路径的匹配
< Route path="/xxxx" component={Demo}/>
-
的最外侧包裹了一个< BrowerRouter>或< HashRouter>
路由组件与一般组件
-
写法不同:
一般组件:< Demo />
路由组件:< Route path="/demo" component={Demo}/>
-
存放位置不同:
一般组件:components
路由组件:pages
-
接收到的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"
NavLink 和 封装 NavLink
- NavLink 可以实现路由链接的高亮,通过activeClassName 指定样式名
- 标签体内容时一个特殊的标签属性
- 通过 this.porps.children 可以获取标签体内容
Switch 的使用
- 通常情况下,path 和component是一一对应的关系
- Switch 可以提高路由匹配效率(单一匹配)
解决多级路径刷新页面样式丢失的问题
- public/index.html 中 引入样式时不写 ./ 写 /(常用)
- public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL%(常用)
- 使用 HashRouter
路由的严格匹配与模糊匹配
- 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
- 开启严格匹配:< Route exact={true} path="/about" component={About}/>
- 严格匹配不要随便开启,需要再开,有些时候开启回导致无法继续匹配二级路由
Redirect的使用
- 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
- 具体编码:
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
嵌套路由
- 注册子路由时要写上父路由的path值
- 路由的匹配时按照注册路由的顺序进行的
向路由组件传递参数
-
params参数
路由链接(携带参数):
<Link to='/demo/test/tom/18'}>详情<Link>
注册路由(声明接收):
<Route path='/demo/test/:name/:age'} component={Test}><Route>
接收参数:
const {id,title} = this.props.match.params
- search参数 路由链接(携带参数):
<Link to='/demo/test?name=tom&age=18'}>详情<Link>
注册路由(无需声明,正常注册即可):
<Route path='/demo/test'} component={Test}><Route>
接收参数:
const {search} = this.props.location
//备注:获取到的search是urlencoded编码字符串,需要借助querystring解析(新版本直接用qs)
- state参数
路由链接(携带参数):
<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}>详情<Link>
注册路由(无需声明,正常注册即可):
<Route path='/demo/test'} component={Test}><Route>
接收参数:
const {id,title} = this.props.location.state
//备注:刷新也可以保留住参数
编程式路由导航
借助this.props.history对象上的API对操作路由跳转、前进、后退
this.props.history.push()
this.props.history.replace()
this.props.history.goBack()
this.props.history.goForward()
this.props.history.go()
BrowserRouter 和 HashRouter的区别
- 底层原理不一样
BrowserRouter 使用的是H5的history API,不兼容IE9及一下版本。
HashRouter 使用的是URL的哈希值
- path表现形式不一样
BrowserRouter 的路径中没有#,例如:localhost:3000/demo/test
HashRouter 的路径包含#,例如:localhost:3000/#/demo/test
- 刷新后对路由state参数的影响
-
BrowserRouter 没有任何影响,因为state保存再history对象中
-
HashRouter 刷新后对导致路由state参数的丢失
-
备注:HashRouter 可以用于解决一些路径错误相关的问题