React 引入
使用create-react-app,或者配置webpack和rollup
yarn add react react-dom
import React from 'react'
import ReactDom from 'react-dom'
React、React函数组件
React.createElement('元素名', {属性名:属性值}/null, '元素内容')
createElement函数的返回值element是一个React元素,也就是创建了一个虚拟DOM对象
函数组件相当于延迟执行createElement函数,延迟生成一个React元素。
通过DOM diff对比虚拟DOM进行局部更新 真正的DOM
JSX
就是将HTML标签 转译成JS 代码 给浏览器解析,浏览器能解析JS 却不认识HTML代码。
若在JSX 里写JS代码,需要加上{},否则解析为字符串
在线翻译工具babel online,查看组件标签 会被翻译 成什么
React 类组件和函数组件
类组件 和 函数组件
如何使用 外部数据props 和 内部数据state
如何绑定事件
类组件:
写法有固定格式,
class Son extends React.Component {
constructor(props) {
// constructor(props) 方法用于初始化
super(props)
// 照抄
this.state = {
// 初始内部数据state,用this指定,等于一个JSON格式的数据;这里表示初始值为n(n=0);用this.state.n可以调用
n: 0
}
}
// 类组件内部定义的函数,用this就可以调用,如this.add()
add(){
this.setState({n: this.state.n + 1})
}
render(){
return (
<div className='son'>
儿子 n: {this.state.n}
<button onClick={()=>this.add()}> +1</button>
</div>
)
}
}
使用props
类组件:直接读取属性
添加:在组件名上添加 变量名={数据} or "数据"<Son VarName={"我是你爸爸"}/>
读:使用 花括号 + this.props.变量名 如{this.props.VarName}
函数组件:直接读取函数的参数
读:函数组件会直接获取外部数据,将之变成一个对象作为形式参数的第一个参数,使用形参props即可
{props.VarName}
使用state
类组件:this.state & this.setState
初始化:在super()后面初始化,this.state = { 名: 值数据 }
读:读对象那样 this.state.名
写:使用setState() 生成一个新的对象,this.setState({n: this.state.n + 1})
——最好像这样不要直接改原来的对象this.setState(n: this.state.n + 1)
注意:setState()是异步的,倘若在某函数作用域中,会等当前作用域代码执行完毕后,再返回setState()的结果。因此setState内最好使用一个带有返回值的函数,就能避开异步困扰了。
函数组件:[n, setN] = useState() 全搞定
setN() 也不会改变n,而是生成一个新的n。
函数组件代替class组件的一点点问题
创建方式const VarName = () =>{} / function VarName() {}
没有state
React v16.8.0 推出了 Hooks API,其中的一个API useState可以解决
没有生命周期
React v16.8.0 推出了 Hooks API,其中的一个API useEffect可以解决
答面试官:这个API的是用来解决副作用的
生命周期?用useEffect(fn, [])
*模拟componentDidMount (组件第一次渲染):
useEffect(()=>{}, [])
表示函数fn只会在第一次渲染时执行,如第一次按+1按钮时fn会执行
*模拟componentDidUpdate (组件任意属性变更):
useEffect(()=>{}, [state变量名])
表示state的变量如n,值改变时,执行fn
类组件 注意事项:
*this.state.n += 1 无效?
n会改变,但UI显示的n没变,调用setState 后UI才会变;
React不会监听state,但是Vue会监听data。
*setState 异步更新UI
推荐使用setState(函数),就能实时更新了
*最好不要this.setState(this.state)
React希望我们不要修改state(不可变数据理念)
最好生成新的state对象,如,setState({})
state里面有多个属性
如果单独更改其中一个属性,那么,
类组件:会分情况覆盖其他属性
属性值不是对象:因为this.setState({})不会创建新对象,只是更改属性值,不会覆盖其他属性
属性值是个对象:所以如果属性值也是个对象,如user={age:18, name:'feifei'},如果只单独修改user的一个属性,那就会覆盖掉另一个属性了
函数组件:会覆盖其他属性
因为setState({}),是创建新对象的意思,因此如果其他属性没有拷贝,那set之后就会消失
如果要写成对象的形式,记得用...把之前的拷贝setState({..., key:value})
总结:有覆盖的时候,就在被覆盖的那个对象里加上...state/...this.state.user,再改需要更改的属性
或者const user = Object.assign({}, this.state.user) === {...this.state.user}
React小知识:
不可变数据:
每次生成的数据一定要生成个新的(这样地址也是新的),否则React会认为还是原来那个地址,就不会触发了。
如何声明变量的类型:只要在变量后面加上:React.类型名称
,类型名称如FunctionComponent、MouseEvent、...
SVG:React早就提供了SVG的所有属性,在组件接收外部数据的类型声明里,用& React.SVGAttributes<SVGElement>即可获取
React的一个库,yarn add classnames,没有这个几乎无法写className;安装依赖yarn add --dev @types/classnames
将数据保存到本地内存:用useEffect() 先读再写,而不是先写再读!!
- 必考:受控组件 V.S. 非受控组件
- 必考:React 有哪些生命周期函数?分别有什么用?(Ajax 请求放在哪个阶段?)
- 必考:React 如何实现组件间通信?
- 必考:shouldComponentUpdate 有什么用?
- 必考:虚拟 DOM 是什么?
- 必考:什么是高阶组件?
- 必考 Redux 是什么?
- React DOM diff 的原理是什么?
- connect 的原理是什么?
React 生命周期函数?
数据请求放在哪个钩子里?
从后台获取数据一定要放在componentDidMount里面调用,因为这时候组件已经加载完了,可以保证数据加载。
React组件挂载时的生命周期,有以下4个:
constructor()
——先调用它后,组件才开始在网页上加载;作组件state初绐化工作的,不能用来加载数据
componentWillMount()
——在这方法里的代码调用setState方法不会触发重渲染,因此不能用来加载数据
render()
——调用执行完,得到虚拟DOM,不是真正的DOM节点
componentDidMount()
——组件加载完成后,再调用它。(在这方法中调用setState方法,会触发重新渲染)
什么是高阶组件?
实际就是个函数,参数是一个组件,返回值也是一个组件。比如connect。ReactRouter里的withRouter
虚拟 DOM 是什么?
虚拟节点,是一个JavaScript对象,React用来模拟DOM节点,然后渲染成真实DOM节点。
模拟:写个div标签的代码就会生成一个对象,对象有3个重要属性,tag是什么标签,props标签的属性,children标签的子节点
<div>
<span></span>
</div>
转译:JSX为createElement函数调用
React.createElement("div",{id:"x"},
React.createElement(...
)
渲染:
render方法,接收虚拟节点,返回真实节点
虚拟节点是文本就创建文本节点;
不是文本,就创建对象{3个属性},根据属性创建createElement
更新 UI 主要就是通过对比(DIFF)旧的虚拟 DOM 树 和新的虚拟 DOM 树的区别完成的。
React.js 相对于直接操作原生 DOM 有很大的性能优势, 很大程度上都要归功于 virtual DOM 的 batching 和 diff。batching 把所有的 DOM 操作搜集起来,一次性提交给真实的 DOM。diff 算法时间复杂度也从 标准的的 Diff 算法的 O(n^3) 降到了 O(n)。
其他优点:虚拟DOM可以跨平台
缺点:所有React事件都绑定到根元素,自动实现事件委托;如果同时用React合成事件和原生DOM事件,可能会有bug。
React 如何实现组件间通信?
父子组件:props予 父传子一个函数,通过函数回调子的值
爷孙组件:两层父子组件通信 or 使用Context API .provider予 .consumer用
任意组件:没有血缘关系,那就把数据放到最顶层的APP里,再下发到目标组件(其实就是全局状态管理)
-
- Redux(react的周边工具)
- Mobx(抄袭Vue响应式思想,自动绑定)
- Recoil(官方的局部状态管理)
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {count: 0}
}
setCount = () => {
this.setState({count: this.state.count + 1})
}
render() {
return (
<div>
<SiblingA
count={this.state.count}
/>
<SiblingB
onClick={this.setCount}
/>
</div>
);
}
}
Redux
- 文档第一句,Redux 是一个JS app 的状态管理容器。
- Redux 库核心概念
- State 存放状态
- Action 表示每一步对数据的改变 type动作类型+payload荷载
- Reducer 传一个旧的State和Action,就产生个新的State
- Dispatch 派发Action
- Middleware
- ReactRedux 库的核心概念
- connect()(component) 把component和store关联起来。接受2次参数,第二次是componnet
- mapStateToProps
- mapDispatchToProps
- 两个常见的中间件redux-thunk redux-promise
Redux、ReduxThunk、ReduxSaga、dva、UmiJS 的区别和联系是什么?
ReactRouter
React Router 是完整的 React 路由解决方案。
react-router-dom
的常用API
:
- BrowserRouter、HashRouter 顶层组件包裹子组件
- Route 给需要渲染的组件路由匹配 `<Route exact path="/tags/:fuck">
<Tag />
</Route>` - Link 把路由放置在包裹的内容上 NavLink 匹配上当前路由的时候给已经渲染的元素添加参数
- switch 包裹Route组件
- redirect 设置默认路由 `<Redirect exact from="/" to="/money" />`
Route 的属性
Route
用于路径的匹配,然后进行组件的渲染,对应的属性如下:
- path 属性:用于设置匹配到的路径
- component 属性:设置匹配到路径后,渲染的组件
- render 属性:设置匹配到路径后,渲染的内容
- exact 属性:开启精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件
两种模式
BrowserRouter、HashRouter
Router
中包含了对路径改变的监听,并且会将相应的路径传递给子组件
BrowserRouter
是history
模式,HashRouter
模式
使用两者作为最顶层组件包裹其他组件
HashRouter
包裹了整应用,
通过window.addEventListener('hashChange',callback)
监听hash
值的变化,并传递给其嵌套的组件
然后通过context
将location
数据往后代组件传递,如下:
BrowserRouter
组件主要做的是通过BrowserRouter
传过来的当前值,通过props
传进来的path
与context
传进来的pathname
进行匹配,然后决定是否执行渲染组件
import React, { Component } from 'react';
import { Provider } from './context'
// 该组件下Api提供给子组件使用
class HashRouter extends Component {
constructor() {
super()
this.state = {
location: {
pathname: window.location.hash.slice(1) || '/'
}
}
}
// url路径变化 改变location
componentDidMount() {
window.location.hash = window.location.hash || '/'
window.addEventListener('hashchange', () => {
this.setState({
location: {
...this.state.location,
pathname: window.location.hash.slice(1) || '/'
}
}, () => console.log(this.state.location))
})
}
render() {
let value = {
location: this.state.location
}
return (
<Provider value={value}>
{
this.props.children
}
</Provider>
);
}
}
export default HashRouter;