1、jsx 基本使用
- 变量、表达式
- class style
- 子元素和组件
2、循环、判断
不解释
3、事件
- bind this
- 关于 event 参数
- 传递自定义参数
1)、this的绑定
改变 this 的指向
- 初始化 bind 的方式 ( 只在初始化 bind )
- 在定义方法的时候 bind 。 不建议使用 (多次bind)
- 通过箭头函数的形式。
- 静态方法 this 永远是 当前实例。(推荐使用)
2)、关于 event 参数
- event.preventDefault() 阻止默认行为
- event.stopPropagetion() 阻止冒泡
- event.target 指向当前元素,即当前元素触发
- event.currentTaget 指向当前元素 假象!!!
react 中的 event 是 react 自己封装的, 不是原生的 event 。可以看 proto.constructor 是 SyntheticEvent (组合事件),可以通过 event.nativeEvent 获取原生 event 。原生 event proto.constructor 是 MouseEvent 。
- event 是 SyntheticEvent (合成事件) 模拟出 DOM 事件的所有能力
- event.nativeEvent 是原生事件
- 所有事件都被挂载到 document
- 和 dom 事件不一样 和 vue 事件也不一样
3)、传递自定义参数
正常传参数,最后一个参数是 event。
4、表单
-
受控组件/非受控组件
-
表单的基本使用 input textare select 用 value
-
checkbox radio 用 checked
受控组件: 在HTML中,标签<input>、<textarea>、<select>的值的改变通常是根据用户输入进行更新。在React中,可变状态通常保存在组件的状态属性中,并且只能使用 setState() 更新,而呈现表单的React组件也控制着在后续用户输入时该表单中发生的情况,以这种由React控制的输入表单元素而改变其值的方式,称为:“受控组件”。
- 通过 value 和 onChange 实现。
- lable 中的for 使用 htmlFor 代替
非受控组件使用场景:
- 必须手动操作 DOM 元素,setState 实现不了
- 文件上传
<input type=file /> - 某个富文本编辑器,需要传入 DOM 元素
受控组件 VS 非受控组件
- 优先使用受控组件,符合 React 设计原则(数据驱动视图)。
- 必须手动操作 DOM ,在使用非受控组件
5、组件使用
1)、props 传递数据
- 在组件上定一个属性
- 通过 this.props.属性 接收
2)、props 传递函数
- 在组件上定一个属性
- 通过 this.props.属性() 调用
3)、props 类型检查
import PropTypes from 'prop-types';
MyComponent.propTypes = {
// JS原始类型,这些全部默认是可选的
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// 可以直接渲染的任何东西,可以是数字、字符串、元素或数组
optionalNode: PropTypes.node,
// React元素
optionalElement: PropTypes.element,
// 指定是某个类的实例
optionalMessage: PropTypes.instanceOf(Message),
// 可以是多个值中的一个
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// 可以是多种类型中的一个
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 只能是某种类型的数组
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// 具有特定类型属性值的对象
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// 具有相同属性值的对象
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),
// 必选条件,可以配合其他验证器,以确保在没有提供属性的情况下发出警告
requiredFunc: PropTypes.func.isRequired,
// 必选条件,提供的属性可以为任意类型
requiredAny: PropTypes.any.isRequired,
// 自定义‘oneOfType’验证器。如果提供的属性值不匹配的它应该抛出一个异常
// 注意:不能使用‘console.warn’ 和 throw
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// 自定义‘arrayOf’或者‘objectOf’验证器。
// 它会调用每个数组或者对象的key,参数前两个是对象它本身和当前key
// 注意:不能使用‘console.warn’ 和 throw
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};
6、setState
- 不可变值/数据
- 可能是异步更新
- 可能会被合并
1)、 不可变值
不能提前对 state 值进行修改,不能应该 state 设置之前的值
不能直接操作 state 值, 只能通过 setState 改变。
// 不可变值(函数式编程,纯函数) - 数组
const list5Copy = this.state.list5.slice()
list5Copy.splice(2, 0, 'a') // 中间插入/删除
this.setState({
list1: this.state.list1.concat(100), // 追加
list2: [...this.state.list2, 100], // 追加
list3: this.state.list3.slice(0, 3), // 截取
list4: this.state.list4.filter(item => item > 100), // 筛选
list5: list5Copy // 其他操作
})
// 注意,不能直接对 this.state.list 进行 push pop splice 等,这样违反不可变值
// 不可变值 - 对象
this.setState({
obj1: Object.assign({}, this.state.obj1, {a: 100}),
obj2: {...this.state.obj2, a: 100}
})
// 注意,不能直接对 this.state.obj 进行属性设置,这样违反不可变值
2)、 可能是异步更新
在 React 库控制时,异步;否则同步。
同步的情况:
setTimeout,自己定义的 DOM 事件 等 非 React库控制时为同步执行
异步的情况:
React 库控制时,比如onChange、onClick、onTouchMove等,这些事件处理程序中的setState都是异步处理的。
React是怎样控制异步和同步的呢?
在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到队列中延时更新,而 isBatchingUpdates 默认是 false,表示 setState 会同步更新 this.state;但是,有一个函数 batchedUpdates,该函数会把 isBatchingUpdates 修改为 true,而当 React 在调用事件处理函数之前就会先调用这个 batchedUpdates将isBatchingUpdates修改为true,这样由 React 控制的事件处理过程 setState 不会同步更新 this.state。
3)、 可能会被合并
-
传入对象,会被合并(类似 Object.assign )。结果值执行一次
this.setState({ count: this.state.count + 1 }) this.setState({ count: this.state.count + 1 }) this.setState({ count: this.state.count + 1 }) -
传入的函数,不会被合并。
// 执行结果是 +3 this.setState((prevState, props) => { return { count: prevState.count + 1 } }) this.setState((prevState, props) => { return { count: prevState.count + 1 } }) this.setState((prevState, props) => { return { count: prevState.count + 1 } })
4)、setState和useState的set函数
- 在正常的react的事件流里(如onClick等)
- setState和useState中的set函数是异步执行的(不会立即更新state的结果)
- 多次执行setState和useState的set函数,组件只会重新渲染一次
- 不同的是,setState会更新当前作用域下的状态,但是set函数不会更新,只能在新渲染的组件作用域中访问到
- 同时setState会进行state的合并,但是useState中的set函数做的操作相当于是直接替换,只不过内部有个防抖的优化才导致组件不会立即被重新渲染
- 在setTimeout,Promise.then等异步事件或者原生事件中
- setState和useState的set函数是同步执行的(立即重新渲染组件)
- 多次执行setState和useState的set函数,每一次的执行都会调用一次render
7、组件生命周期
1)、单组件
挂载时:constructor -> render -> componentDidMount
更新时:props、setState等 -> render -> componentDidUpdate
常用生命周期:
不常用生命周期:
React16.4版本之后使用了新的生命周期,它使用了一些新的生命周期钩子(getDerivedStateFromProps、getSnapshotBeforeUpdate),并且即将废弃老版的3个生命周期钩子(componentWillMount、componentWillReceiveProps、componentWillUpdate)
如图所示,我们可以看到,在组件第一次挂载时会经历:
构造器(constructor)=》修改state属性(getDerivedStateFromProps)=》组件挂载渲染(render)=》组件挂载完成(componentDidMount)
组件内部状态更新:
更新state属性(getDerivedStateFromProps)=》判断组件是否更新(shouldComponentUpdate)=》组件更新渲染(render)=》(getSnapshotBeforeUpdate)=》组件更新完成(componentDidUpdate)
组件卸载时执行:
组件销毁(componentWillUnmount)
**注意:
**
新版本使用了getDerivedStateFromProps代替了componentWillMount、componentWillReceiveProps、componentWillUpdate三个钩子函数,如果在旧版本中使用将会警告提示。它必须要return一个null或者对象,并且会影响初始化的值以及修改的值。存在只有一个目的:让组件在 props 变化时更新 state。
getSnapshotBeforeUpdate钩子必须与componentDidUpdate搭配使用否则会报错。在旧版本中使用将会警告提示。必须要return一个null或者任何值,它将在最近一次渲染输出(提交到DOM节点)之前调用。
生命周期新增函数详解
static getDerivedStateFromProps(getDSFP)
首先这个新的方法是一个静态方法,在这里不能调用this,也就是一个纯函数。它传了两个参数,一个是新的nextProps ,一个是之前的prevState,所以只能通过prevState而不是prevProps来做对比,它保证了state和props之间的简单关系以及不需要处理第一次渲染时prevProps为空的情况。也基于以上两点,将原本componentWillReceiveProps里进行的更新工作分成两步来处理,一步是setState状态变化,更新 state在getDerivedStateFromProps里直接处理。
旧的React中componentWillReceiveProps方法是用来判断前后两个props是否相同,如果不同,则将新的props更新到相应的state上去。在这个过程中我们实际上是可以访问到当前props的,这样我们可能会对this.props做一些奇奇怪怪的操作,很可能会破坏state数据的单一数据源,导致组件状态变得不可预测。
而在getDerivedStateFromProps中禁止了组件去访问 this.props,强制让开发者去比较nextProps与prevState中的值,以确保当开发者用到getDerivedStateFromProps这个生命周期函数时,就是在根据当前的props来更新组件的state,而不是去访问this.props并做其他一些让组件自身状态变得更加不可预测的事情。
getSnapshotBeforeUpdate
在React开启异步渲染模式后,在执行函数时读到的DOM元素状态并不一定和渲染时相同,这就导致在componentDidUpdate中使用的DOM元素状态是不安全的(不一定是最新的),因为这时的值很有可能已经失效了。
与componentWillMount不同的是,getSnapshotBeforeUpdate会在最终确定的render执行之前执行,也就是能保证其获取到的元素状态与componentDidUpdate中获取到的元素状态相同。
这个方法并不常用,但它可能出现在UI处理中,如需要以特殊方式处理滚动位置的聊天线程等。并且会返回snapshot的值或null。
生命周期修改的深层原因
因为React 16引入了Fiber机制,把同步的渲染流程进化为了异步的渲染流程,这么做的原因是同步渲染流程有个弊端:一旦开始就不能停下,大工作量的渲染任务执行时,主线程会被长时间的占用,浏览器无法即时响应与用户的交互。
Fiber机制会把渲染任务拆解为多个小任务,并且每执行完一个小任务,就把主线程的执行权交出去,也就解决了上面的弊端。
然而,采用Fiber机制进行渲染时,render阶段没有副作用,可以被暂停,终止或重新启动。就是这个重新启动,会导致工作在render阶段的componentWillMount、componentWillReceiveProps、componentWillUpdate存在重复执行的可能,所以它们几个必须被替换掉。
2)、父子组件
和 vue 的完全一样。
8、React 高级属性 ---- 性能优化
1)、shouldComponentUpdate
返回 true ,表示可以渲染。返回 false 不可渲染。默认返回 true 。
shouldComponentUpdate(nextProps, nextState) {
if (nextState.count !== this.state.count) {
return true // 可以渲染
}
return false // 不重复渲染
}
既然有个功能 为什么默认返回 true , 为什么可定制化。
- 默认父组件更新,子组件无条件更新
2)、PureComponent (纯组件) 和 React.memo
- PureComponent, shouldComponentUpdate 中实现了浅比较 (class 组件)
- memo 函数组件中的 PureComponent
- 浅比较已经适应大部分情况(尽量不要做深度比较)
3)、不可变值 immutable.js
- 彻底拥抱 不可变值
- 基于共享数据(不是深拷贝),速度好
- 有一定学习和迁移成本,按需引用
什么是 Immutable Data
- mmutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象
- Immutable 实现的原理是 Persistent Data Structure (持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变
- 同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗, Immutable 使用了 Structural Sharing···· (结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
为什么要在React.js中使用Immutable
- 它是一个完全独立的库,无论基于什么框架都可以用它。意义在于它弥补了 Javascript 没有不可变数据结构的问题
- 由于是不可变的,可以放心的对对象进行任意操作。在 React 开发中,频繁操作state对象或是 store ,配合 immutableJS 快、安全、方便
- 熟悉 React.js 的都应该知道, React.js 是一个 UI = f(states) 的框架,为了解决更新的问题, React.js 使用了 virtual dom , virtual dom 通过 diff 修改 dom ,来实现高效的 dom 更新。
- 但是有一个问题。当 state 更新时,如果数据没变,你也会去做 virtual dom 的 diff ,这就产生了浪费。这种情况其实很常见
- 当然你可能会说,你可以使用 PureRenderMixin 来解决呀, PureRenderMixin 是个好东西,我们可以用它来解决一部分的上述问题
- 但 PureRenderMixin 只是简单的浅比较,不使用于多层比较。那怎么办?自己去做复杂比较的话,性能又会非常差
- 方案就是使用 immutable.js 可以解决这个问题。因为每一次 state 更新只要有数据改变,那么 PureRenderMixin 可以立刻判断出数据改变,可以大大提升性能
9、React 高级属性 ---- 高阶组件 HOC (抽离公共逻辑)
一个高阶组件只是一个包装了另外一个 React 组件的 React 组件。这种形式通常实现为一个函数,本质上是一个类工厂(class factory)
高阶组件的作用就是实现组件复用,节省内存
- 先定义一个函数式组价,传入一个参数,这个参数就是组件
- 组件内返回一个class类组件,类名可以写也可以不写
- 类组件内部可以写方法,数据,然后将参数当做组件返回出去,并将方法或者数据,传个这个组件
例子1:
import React,{ Component } from 'react';
const Hoc = ( Comp ) =>{//参数首字母必须大写,必须要有返回值,在下面使用
return class banner extends Component{ //类名可以省略,省略的话标签名就是以temp或者其他的代替,必须要有返回值
banner = () => {//这里是实现某个功能的函数代码
return 'zhangyue'
}
render () {
return (
<Comp banner = { this.banner }></Comp>//将参数当做一个组件返回出去
)
}
}
}
class A extends Component{
render () {
return (
<div>
<p> A组件 </p>
{ this.props.banner() }//在下面使用了高阶组件后,这里就可以直接使用里面的方法了
</div>
)
}
}
class B extends Component{
render () {
return (
<div>
<p> B组件 </p>
{ this.props.banner() }
</div>
)
}
}
const HocA = Hoc(A)//组件名必须首字母大写,将组件名当参数传进去,这样这个组件就有高阶组件内的方法了
const HocB = Hoc(B)
class C extends Component{
render () {
return (
<div>
<p> C组件 </p>
<HocA></HocA>//这里使用的高阶组件
<HocB></HocB>
</div>
)
}
}
export default C
例子2:
import React from 'react'
// 高阶组件
const withMouse = (Component) => {
class withMouseComponent extends React.Component {
constructor(props) {
super(props)
this.state = { x: 0, y: 0 }
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
})
}
render() {
return (
<div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
{/* 1. 透传所有 props 2. 增加 mouse 属性 */}
<Component {...this.props} mouse={this.state}/>
</div>
)
}
}
return withMouseComponent
}
const App = (props) => {
const a = props.a
const { x, y } = props.mouse // 接收 mouse 属性
return (
<div style={{ height: '500px' }}>
<h1>The mouse position is ({x}, {y})</h1>
<p>{a}</p>
</div>
)
}
export default withMouse(App) // 返回高阶函数
10、React 高级属性 ---- Render Props (抽离公共逻辑)
给组件添加一个值为函数的属性,这个函数可以在组件渲染(render)的时候调用
核心思想: 通过一个函数将 class 组件的 state 作为 props 传递给纯函数组件。
import React from 'react'
import PropTypes from 'prop-types'
class Mouse extends React.Component {
constructor(props) {
super(props)
this.state = { x: 0, y: 0 }
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
})
}
render() {
return (
<div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
{/* 将当前 state 作为 props ,传递给 render (render 是一个函数组件) */}
{this.props.render(this.state)}
</div>
)
}
}
Mouse.propTypes = {
render: PropTypes.func.isRequired // 必须接收一个 render 属性,而且是函数
}
const App = (props) => (
<div style={{ height: '500px' }}>
<p>{props.a}</p>
<Mouse render={
/* render 是一个函数组件 */
({ x, y }) => <h1>The mouse position is ({x}, {y})</h1>
}/>
</div>
)
/**
* 即,定义了 Mouse 组件,只有获取 x y 的能力。
* 至于 Mouse 组件如何渲染,App 说了算,通过 render prop 的方式告诉 Mouse 。
*/
export default App
HOC VS Render Porps
- HOC: 模式简单,但会增加组件层级
- Render Porps: 代码简洁,学习成本较高
- 按需使用吧
11、React 高级属性 ---- 函数组件
定义一个函数 只接收 props 然后 return 内容。
-
纯函数,输入 props ,输出 JSX。
-
没有实例,没有生命周期,没有 state。
-
不能扩展其他方法。
12、React 高级属性 ---- ref
- React.createRef() 创建 ref
- this.myRef.current 获取 DOM 元素
13、React 高级属性 ---- Portals (插槽)
Portals 提供了一种能让子节点渲染到父组件之外的方式。
React.createPortal(child,container);
第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或碎片。第二个参数(container)则是一个 DOM 元素,挂载到的位置。
render() {
// // 正常渲染
// return <div className="modal">
// {this.props.children} {/* vue slot */}
// </div>
// 使用 Portals 渲染到 body 上。
// fixed 元素要放在 body 上,有更好的浏览器兼容性。
return ReactDOM.createPortal(
<div className="modal">{this.props.children}</div>,
document.body // DOM 节点
)
}
Portals 使用场景:
- overflow: hidden (父组件设置了 BFC , BFC 不限制子组件的展示,让子组件逃离父组件 )
- 父组件 z-index 值太小 (让子组件逃离父组件设置 合适的 z-index)
- fixed 需要放在 body 第一层级的
一般都是应对 css 样式布局问题。
14、React 高级属性 ---- context
跨组件传递数据,解决 props 传递繁琐, redux 小题大做问题。
context api给出三个概念:React.createContext()、Provider、Consumer;
-
React.createContext() : 创建context对象
-
Provider: 数据的生产者,通过value属性接收存储的公共状态,来传递给子组件或后代组件。<Provider value={/* some value */}>
-
Consumer : 数据的消费者,通过订阅Provider传入的context的值,来实时更新当前组件的状态
import React from 'react' // 创建 Context 填入默认值(任何一个 js 变量) const ThemeContext = React.createContext('light') // 底层组件 - 函数式组件 function ThemeLink (props) { // const theme = this.context // 会报错。函数式组件没有实例,即没有 this // 函数式组件可以使用 Consumer return <ThemeContext.Consumer> { value => <p>link's theme is {value}</p> } </ThemeContext.Consumer> } // 底层组件 - class 组件 class ThemedButton extends React.Component { // 指定 contextType 读取当前的 theme context。 // static contextType = ThemeContext // 也可以用 ThemedButton.contextType = ThemeContext render() { const theme = this.context // React 会往上找到最近的 theme Provider,然后使用它的值。 return <div> <p>button's theme is {theme}</p> </div> } } ThemedButton.contextType = ThemeContext // 指定 contextType 读取当前的 theme context。 // 中间的组件再也不必指明往下传递 theme 了。 function Toolbar(props) { return ( <div> <ThemedButton /> <ThemeLink /> </div> ) } class App extends React.Component { constructor(props) { super(props) this.state = { theme: 'light' } } render() { return <ThemeContext.Provider value={this.state.theme}> <Toolbar /> <hr/> <button onClick={this.changeTheme}>change theme</button> </ThemeContext.Provider> } changeTheme = () => { this.setState({ theme: this.state.theme === 'light' ? 'dark' : 'light' }) } } export default App
15、React 高级属性 ---- 异步组件
- import() vue 中常用
- React.lazy
- React.Suspense
组件比较大,路由懒加载的时候 使用
import React from 'react'
const ContextDemo = React.lazy(() => import('./ContextDemo'))
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return <div>
<p>引入一个动态组件</p>
<hr />
<React.Suspense fallback={<div>Loading...</div>}>
<ContextDemo/>
</React.Suspense>
</div>
// 验证方式:
// 1. 强制刷新,可看到 loading (看不到就限制一下 chrome 网速)
// 2. 看 network 的 js 加载
}
}
export default App`
16、Redux 使用
1)、基本概念
应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。 惟一改变 state 的办法是触发 action,一个描述发生什么的对象。 为了描述 action 如何改变 state 树,你需要编写 reducers。
(1)、store state
Redux 应用只有一个单一的 store。唯一的一个。
Store 有以下职责:
- 维持应用的 state;
- 提供 getState() 方法获取 state;
- 提供 dispatch(action) 方法更新 state;
- 通过 subscribe(listener) 注册监听器;
- 通过 subscribe(listener) 返回的函数注销监听器。
(2)、action
Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store。
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
Action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。
import { ADD_TODO, REMOVE_TODO } from '../actionTypes'
(3)、reducer
Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。
reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。
(previousState, action) => newState
例子:
function todoApp(state = initialState, action) {
// 这里暂不处理任何 action,
// 仅返回传入的 state。
return state
}
永远不要在 reducer 里做这些操作:
- 修改传入参数;
- 执行有副作用的操作,如 API 请求和路由跳转;
- 调用非纯函数,如 Date.now() 或 Math.random()。
2)、单项数据流
严格的单向数据流是 Redux 架构的设计核心。
Redux 应用中数据的生命周期遵循下面 4 个步骤:
- 调用 store.dispatch(action)。
- Redux store 调用传入的 reducer 函数。
- 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。
- Redux store 保存了根 reducer 返回的完整 state 树。
3)、react-redux
(1)、
使组件层级中的 connect() 方法都能够获得 Redux store。正常情况下,你的根组件应该嵌套在 中才能使用 connect() 方法。
ReactDOM.render(
<Provider store={store}>
<MyRootComponent />
</Provider>,
rootEl
)
(2)、connect
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
- mapStateToProps: 这个函数允许我们将 store 中的数据作为 props 绑定到组件上。
- mapDispatchToProps: 将 action 作为 props 绑定到组件上,也会成为组件自己的 props。
- 不管是 stateProps 还是 dispatchProps,都需要和 ownProps merge 之后才会被赋给组件。connect 的第三个参数就是用来做这件事。通常情况下,你可以不传这个参数,connect 就会使用 Object.assign 替代该方法。
- [options] (Object) 如果指定这个参数,可以定制 connector 的行为。一般不用。
(3)、mapStateToProps(state, ownProps), mapDispatchToProps(dispatch, ownProps)
mapStateToProps(state, ownProps):
- 这个函数的第一个参数就是 Redux 的 store,我们从中摘取了 count 属性。你不必将 state 中的数据原封不动地传入组件,可以根据 state 中的数据,动态地输出组件需要的(最小)属性。
- 函数的第二个参数 ownProps,是组件自己的 props。有的时候,ownProps 也会对其产生影响。
当 state 变化,或者 ownProps 变化的时候,mapStateToProps 都会被调用,计算出一个新的 stateProps,(在与 ownProps merge 后)更新给组件。
mapDispatchToProps(dispatch, ownProps):
- 第一个参数就是 dispatch 方法
- 同上
(3)、
4)、异步 action
需要引入中间件 redux-thunk
let store = createStore(
combineReducers({...home,...log}),
applyMiddleware(thunk,...),//加入中间件redux-thunk,applyMiddleware就是Redux的一个原生方法,将所有中间件组成一个数组,依次执行。
);
原来的store.dispatch方法只能接收一个普通的action对象作为参数,当我们加入了ReduxThunk这个中间件之后,store.dispatch还可以接收一个方法作为参数,这个方法会接收到两个参数,第一个是dispatch,等同于store.dispatch,第二个是getState,等同于store.getState,也就是说,现在可以这样来触发INCREASE:
store.dispatch((dispatch, getState) => dispatch({type: 'INCREASE'}));
5)、中间件
中间件 对原 dispatch 进行改造,获取原 dispatch 处理逻辑 然后传给 真正的 dispatch
17、React-router
- 路由模式 (hash , H5 history), 同 vue-router
- 路由配置 (懒加载,动态路由),同 vue-router
18、函数是编程
- 纯函数
- 不可变值
19、Vdom 和 diff 算法
回顾:
- h 函数: 传入 tag 属性 子节点 返回 vnode 数据结构
- vnode 数据结构
- patch 函数
- 只比较同一层级,不跨级比较
- tag 不相同,则直接删掉重建,不在深度比较
- tag 和 key 两者都相同 则认为是相同节点,不在深度比较
20、合成事件
- event 是 SyntheticEvent (合成事件) 模拟出 DOM 事件的所有能力
- 所有的事件都挂载到 document 上
- 原生和合成执行顺序:首先DOM事件监听器被执行,然后事件继续冒泡至document,合成事件监听器被执行。
- 和 vue 事件不同,和 原生 Dom 不同
event内容
- event.preventDefault() 阻止默认行为
- event.stopPropagetion() 阻止冒泡
- event.nativeEvent 是原生事件
- event.target 指向当前元素,即当前元素触发
- event.currentTaget 指向当前元素 假象!!!
为什么要使用合成事件?
- 更好的兼容性和跨平台
- 挂载到 document , 减少内存消耗,避免频繁解绑
- 方便事件的统一管理 (如事务机制)
16和17变化
16版本先执行原生事件,当冒泡到document时,统一执行合成事件,17版本在原生事件执行前先执行合成事件捕获阶段,原生事件执行完毕执行冒泡阶段的合成事件,通过根节点来管理所有的事件- 原生的阻止事件流会阻断合成事件的执行,合成事件阻止后也会影响到后续的原生执行
在 React16 中,对 document 的事件委托都委托在冒泡阶段,当事件冒泡到 document 之后触发绑定的回调函数,在回调函数中重新模拟一次 捕获-冒泡 的行为,所以 React 事件中的e.stopPropagation()无法阻止原生事件的捕获和冒泡,因为原生事件的捕获和冒泡已经执行完了。
在 React17 中,对 React 应用根 DOM 容器的事件委托分别在捕获阶段和冒泡阶段。即:
- 当根容器接收到捕获事件时,先触发一次 React 事件的捕获阶段,然后再执行原生事件的捕获传播。所以 React 事件的捕获阶段调用
e.stopPropagation()能阻止原生事件的传播。 - 当根容器接受到冒泡事件时,会触发一次 React 事件的冒泡阶段,此时原生事件的冒泡传播已经传播到根了,所以 React 事件的冒泡阶段调用
e.stopPropagation()不能阻止原生事件向根容器的传播,但是能阻止根容器到页面顶层的传播。
21、setState 和 batchUpdate
1)、setState
- 有时异步(普通使用),有时同步(非 react setTimeout Dom 事件)
- 有时合并 (对象形式),有时不合并 (函数形式)
真正判断是否同步或者异步:
- 看是否命中 batchUpdate 机制
- 判断 isBatchUpdates
2)、batchUpdate 机制
在React的生命周期和合成事件执行前后都有相应的钩子,分别是pre钩子和post钩子,pre钩子会调用batchedUpdate方法将isBatchingUpdates变量置为true,开启批量更新,而post钩子会将isBatchingUpdates置为false
isBatchingUpdates变量置为true,则会走批量更新分支,setState的更新会被存入队列中,待同步代码执行完后,再执行队列中的state更新。
而在原生事件和异步操作中,不会执行pre钩子,或者生命周期的中的异步操作之前执行了pre钩子,但是pos钩子也在异步操作之前执行完了,isBatchingUpdates必定为false,也就不会进行批量更新。
3)、transaction 事务机制
我们可以这样理解事务,react中用事务执行方法,就是用wrapper(称之为套套吧)把方法包裹起来,然后每个wapper中都提供一个initialize方法和一个close方法,当需要使用事务调用一个方法,例如上图中的anyMethod时,使用事务提供的perform方法,将需要执行的方法传入,这个时候就会按顺序执行wrapper.initalize,anyMethod,wrapper.close,而且,事务还支持多个事务的嵌套,当执行方法被多个wapper包裹时,事务会先按顺序执行所有的initalize方法,再执行anyMethod,最后按顺序执行所有的close函数,例如上图就表示会按以下顺序执行wrapper1.initalize,wrapper2.initalize,anyMethod,wrapper1.close,wrapper2.close
22、组件渲染过程
创建过程:
- props state
- render() 生成 vnode
- patch(elem,vnode) 生成节点
注:patch react 中不一定是 patch 函数生成的,有可能是其他名字,原理和 patch 函数相同。
更新过程:
- setState(newState) ---> dirtyComponents (有可能子组件)
- render() 生成 newVnode
- patch(elem,vnode)创建节点
patch 拆分两个阶段:
- renconciliation 阶段----执行 diff 算法,纯 js 计算
- commit 计算----将 diff 结果渲染到 DOM
如果不拆分可能存在的问题:
- js 是单线程,且和 DOM 渲染共用一个线程
- 当组件足够复杂,组件更新时计算和渲染都压力大
- 同事再有 DOM 操作需求(动画、鼠标拖拽等),将卡顿..
解决方案 fiber
- renconciliation 阶段进行任务拆分(commit 无法拆分)
- DOM 需要渲染的时暂停,空闲时恢复
- 通过 window.requestdleCallback(浏览器有兼容的问题) 判断 DOM 是否渲染完成
fiber架构思想:
改变了之前react的组件渲染机制,新的架构使原来同步渲染的组件现在可以异步化,可中途中断渲染,执行更高优先级的任务。释放浏览器主线程,
前面是react16以前的组建渲染方式。这就存在一个问题,
如果这是一个很大,层级很深的组件,react渲染它需要几十甚至几百毫秒,在这期间,react会一直占用浏览器主线程,任何其他的操作(包括用户的点击,鼠标移动等操作)都无法执行。
23、jsx 本质
// https://www.babeljs.cn/
// JSX 基本用法
const imgElem = <div id="div1">
<p>some text</p>
<img src={imgUrl}/>
</div>
// JSX style
const styleData = { fontSize: '30px', color: 'blue' }
const styleElem = <p style={styleData}>设置 style</p>
// JSX 加载组件
const app = <div>
<Input submitTitle={onSubmitTitle}/>
<List list={list}/>
</div>
// JSX 事件
const eventList = <p onClick={this.clickHandler}>
some text
</p>
// JSX list
const listElem = <ul>{this.state.list.map((item, index) => {
return <li key={item.id}>index {index}; title {item.title}</li>
})}</ul>
// 总结
React.createElement('div', null, [child1, child2, child3])
React.createElement('div', {...}, child1, child2, child3)
React.createElement(List, null, child1, child2, '文本节点')
// h 函数
// 返回 vnode
// patch
第一个参数是 tag 标签 或者 组件 (如果是组件 继续拆分 为 tag 标签) 第二个参数是 属性 有可能是 null 后边的参数是子元素,有数组和正常参数,也有文本节点
- React.createElement 即 h 函数,返回 vnode 节点
- 第一个参数是 tag 标签 或者 组件
- 组件名首字母大写,html tag 是小写
24、React 性能优化
- 渲染列表时加 key
- 自定义事件、Dom 事件及时销毁
- 合理使用异步组件
- 减少函数 bind this 的次数
- 合理使用 shouldComponentUpdate PureComponent (纯组件) 和 React.memo
- 合理使用 Immutable.js ( 不可变值 )
- webpack
- 前端通用的性能优化
- 使用 ssr
25、React 和 Vue 的区别
共同点:
- 都支持组件化
- 都是数据驱动视图
- 都使用 Vdom 操作 DOM
区别:
- React 使用 JSX 拥抱 js, Vue 使用模板拥抱 html
- React 函数式编程, Vue 是声明式编程
- React 更多的是自力更生 (比较灵活),Vue 把想要的都给你 (比较固定,使用简单)