新版本的react中,删除了一些生命周期,目前推荐使用的生命周期为如下几种:前端
首次渲染:vue
一、constructor,可写可不写,主要实现绑定this,初始化state的做用node
二、render,应该是个纯函数,绝对不能有反作用产生,好比setState。这个函数的做用仅仅是返回jsxreact
三、componentDidMountwebpack
更新:web
一、shouldComponentUpdate(nextProps,nextState):必须返回true或false,决定在此次更新中是否要从新渲染。面试
二、render算法
三、componentDidUpdateredux
卸载:设计模式
componentWillUnmount:这个方法一般用来作一些清理工做,如,删除定时器,删除一些非react手段产生的dom。
一、函数组件:接收props,输入jsx;没有状态,没有生命周期,没有实例。
二、受控组件:表单组件的value是受控于state的
三、非受控组件:非受控于state,经过ref来获取到原生的value。当咱们必需要操做dom时,使用非受控组件,例上传文件
ReactDOM.createPortal(child, container) ,由ReactDom提供的接口。 能够实现将子节点渲染到父组件DOM层次结构以外的DOM节点。子组件可以在视觉上 “跳出(break out)” 其容器。例如,对话框、hovercards以及提示框
const myComponent = React.lazy(()=>import('./myComponent.js'))
<React.Suspense fallback={<div>loading...</div>}>
<myComponent/>
</React.Suspense>
复制代码
ref是react提供的一种访问dom节点或组件实例的方法,给标签设置ref,获取的是dom对象;给组件设置ref,获取的是组件的实例。
一、当用到组件上时,必须是类组件,由于函数组件没有实例。
二、在函数组件中,能够用createRef,也能够用useRef。这两种方法均可以获取相应的dom节点。可是useRef除了能够获取dom节点,还能够用来保存一个在整个生命周期中不变的值,这一点在函数组件中很是重要!由于函数组件从新渲染后会返回新的值,这也是二者的区别!适合配合hook组件来使用
// class组件
myRef1 = React.createRef()
myRef2 = React.createRef()
componentDidMount(){
this.myRef1.current.focus()
}
render(){
return <div> <input ref={this.myRef1}>ref的用法</input> <Child ref={this.myRef2}/> </div>
}
// const myRef = React.useRef(初始值)
返回一个对象 {current:初始值}
复制代码
HOC是高阶组件,高阶组件就是一个函数接收一个组件做为输入,而后返回一个新的组件做为结果。高阶组件经过使用方式,能够分为两类,代理方式的高阶组件和继承方式的高阶组件。
代理方式高阶组件:说白了就是在新组件中render接收的参数组件。在这一过程当中,咱们能够对props进行处理,并传入参数组件中,以此来达到不一样的效果。典型的例子就是react-redux的原理。执行connect函数,返回一个高阶组件,他在接收一个组件做为参数。
高阶组件用于扩展的主要方式就是经过修改props
connect(mapStateToProps,mapDispatchToPrps)(Child)
复制代码
继承方式高阶组件:比较复杂,优先使用代理方式。
context就是一种能够从父组件或顶级组件,向全部后代组件传递信息的方法。常见的使用场景就是更改主题、语言等。若是是复杂的信息,那么就用redux
使用方法三步:建立,分发,接收
顶层组件用Provider包裹,并传递value,不能传其余的。
函数组件能够用Consumer传children或者用hook:useContext
类组件直接将类的私有属性contentType设置为建立的context
import React, { useContext } from 'react'
import ReactDOM from 'react-dom'
// 一、建立一个context
const MyContext = React.createContext('mm')
// 函数组件
function Child1() {
return (
<MyContext.Consumer> {(context) => { return <div>{context}</div> }} </MyContext.Consumer>
)
}
// 类组件
class Child2 extends React.Component {
static contextType = MyContext
render() {
return <div>{this.context}</div>
}
}
// hook用法
function Child3() {
const context = useContext(MyContext)
return <div>{context}</div>
}
class App extends React.Component {
state = {
name: 'yy'
}
click = () => {
this.setState((state, props) => {
console.log(state, props)
return { name: state.name + 1 }
})
}
render() {
return (
<MyContext.Provider value={this.state.name}> // 用Provider包裹 <Child1 /> <Child2 /> <Child3 /> <button onClick={this.click}>++++</button> </MyContext.Provider>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
复制代码
SCU默认返回true,可是若是想让react进行浅比较,那就用purecomponent,这样react就会自动帮咱们进行浅比较。由于函数组件没有生命周期,因此就用memo,他会保存一个副本,若是判断新props不会产生影响,则返回这个缓存副本,而不会从新渲染组件。
// 类组件
class App extends React.pureComponent{}
// 函数组件
function MyComponent(props){...}
function likeSCU(prevProps,nextProps){
// 判断新老props是否对结果有影响,返回true或false
}
export default React.memo(MyComponent,likeSCU)
复制代码
虚拟dom本质上就是JavaScript对象,整个过程以下:
一、首先,babel会对jsx进行转化成React.createElement('tag'/组件名,null/{属性},[child1,child2...]/child1,child2...)形式。
2、执行createElement方法后生成vnode,这个对象会有三个属性:标签类型、标签属性和子节点。其中子节点是一个数组,若是只是文字,那就直接字符串的形式,若是也是一个标签,则用对象的形式,对象里的属性仍然是以上三种。
3、生成完这个对象后,就经过patch函数,渲染成真实的dom。
四、当对数据进行修改时,从新执行React.createElemnet方法,会生成一个新的对象。此时运用diff算法,将新老dom进行对比,对比的大体流程为:判断节点类型,节点的属性,节点的子节点是否一致,若是不一致,则生成一个标识并存储。
五、当遍diff算法运行完毕,就会生成一个数组,里面是标志着修改点的标识。这时候,再遍历这个数组,经过每一个标识,来进行不一样的操做,来修改真实的dom。从而达到以最少的操做来更新dom。
原理在于优化diff算法。
当更新时组件时,会调用diff算法。可是diff算法并无那么智能,当咱们渲染列表时,假如咱们只是更改了顺序,而没有作其余任何操做,diff算法对比新旧vdom,会认为是props改变了,就会从新渲染。
为了解决这个问题,引入了key,key是react中的保留字段,他要是惟一的,不能变的,不能用index,由于若是数组的顺序变了,diff时就会产生一些错乱。若是加上了key,react就能知道新旧vdom只是换了位置,他就会采用另一种方式,好比建立新的,删除旧的,或者其余方式。固然,其余的组件也会触发更新,只不过不会改变props了。
一、什么是合成事件系统
react会把全部事件绑定到结构的最外层,使用一个统一的事件监听器,他经过一个映射来保存全部的事件监听和相应的处理函数,当组件挂载和卸载时,就会在这个事件监听器上增长或删除映射。
二、事件系统有什么好处
解决全部浏览器的兼容性问题(好比在IE9如下,阻止事件冒泡的方法不一样,但react进行统一兼容。还有在低版本的IE浏览器上,不能直接获取事件对象,react也进行了兼容处理,直接在处理函数中获得事件对线)、简化事件处理和回收机制,提高效率(原理相似于事件委托机制)。
3、如何使用原生事件
react中event.nativeEvent是原生的事件对象,必定要在组件销毁的时候手动清除,不然会致使内存泄漏。
4、合成事件与原生事件一同使用的问题
合成事件其实是绑定到最外层,经过e.stopPropagation()方法来阻止合成事件冒泡,可是这种方式没法阻止原生事件冒泡。因此,当外层一样绑定了一个原生事件时,就会被触发。尽可能不要原生和合成事件混用。若是混用,能够在原生事件中经过e.target来判断来源,进行处理。
5、合成事件只支持了事件冒泡机制,而没有事件捕获机制。合成事件并不能支持全部种类的事件,它只是原生事件类型的一个子集。
7、与vue中不一样,vue中target和currentTarget都是写事件的那个元素,而react中target是触发的元素,currentTarget是绑定事件的元素,也就是document
event.nativeEvent.target 触发事件的元素--->写事件的元素
event.nativeEvent.currentTarget 绑定事件的元素--->document
复制代码
一、只能经过setstate方法改变state的值
不能直接更改state的值。也不能在setstate时使用数组push、pop、splice等方式直接改变原数据的方法,对象也不能直接改变其属性值。
实际上,这样写有时候页面也能正常展现,可是可能会有很严重的bug。例如:当咱们在用SCU优化时,若是咱们判断新老name属性是否相等,而在父组件上,若是咱们向下面这样写,至关于先改变了state,再用setState触发更新,这会致使在子组件中nextProps和props是彻底同样的,就不会更新。例如:
this.state.name = 'xxx'
this.setState({name:this.state.name})
或
this.setState({arr:this.state.arr.push(111)})
复制代码
// 错误1
this.state.count++
// 错误2
this.setState({arr:this.state.arr.push/pop/shift/slice...(直接改变原数组的)(1)})
// 错误3
this.state.obj.name= 'xxx'
this.setState({obj:this.state.obj})
复制代码
二、setstate有两种写法
// 第一个参数是要改变的对象,第二个参数是回调函数,会在改变完成后执行
this.setState({},()=>{})
// 第一个参数是函数,接受两个参数,第一个函数在更新前运行,第二个在更新后运行
this.setState((state,props)=>{},(state,props)=>{})
复制代码
三、setState异步仍是同步
setState有时是同步,有时是异步。关键在与batch update机制,react并非执行全部代码时都能启动这个机制,只有在受到react管理的入口中,才会触发机制,如:生命周期(及其中调用的函数)、react中注册的事件(及其中调用的函数)。而setTimeout、setInterval、自定义的dom事件,这些并非受到react管理的,这些没有batchupdate机制。
batchupdate即批量更新,react为了提升渲染效率,会把更新操做集中起来处理,因此,在函数运行完毕前,react没有着急去setState,而是等到运行结束,把全部的setState集中起来统一处理。因此,就变成了异步。
batch update机制
click=()=>{
// 开始,处于batch update
// 能够想象成,在函数开始执行时 变量isBatchingUpdate = true
// 由于处于batch update中,因此为异步的
this.setState({count:this.state.count+1},()=>{console.log(this.state.count)})
console.log(this.state.count)
// 结束,变量isBatchingUpdate = false
}
click=()=>{
// 开始,处于batch update
// 能够想象成,在函数开始执行时 变量isBatchingUpdate = true
// 由于setTimeout是异步的,因此并无当即执行setState,而当执行setState时,isBatchingUpdate已经为false,即不处于batch update阶段,因此会同步执行代码
setTimeout(()=>{
this.setState({count:this.state.count+1})
// 此处能够得到最新的值
console.log(this.state.count)
})
// 结束,变量isBatchingUpdate = false
}
// 这个原理与上面的相同
document.body.addEventListen('click',()=>{
this.setState({count:this.state.count+1})
// 此处能够得到最新的值
console.log(this.state.count)
})
复制代码
一道经典面试题:
五、setState多个可能被合并成一个
这样写会被合并,为何?
前面说过,若是命中了batch机制,会在batch结束时统一进行处理,而这个时候,this.state.count的值都是同样的,因此,至关于执行了三遍this.setState(0+1),可想而知,其实最后一次的才会起到最终做用。
this.setState({count:this.state.count+1})
this.setState({count:this.state.count+1})
this.setState({count:this.state.count+1})
而若是时函数写法,就不会被合并。由于函数参数中的state老是接受最新的state
this.setState((state,props)=>({count:state.count+1}))
this.setState((state,props)=>({count:state.count+1}))
this.setState((state,props)=>({count:state.count+1}))
复制代码
一、shouldComponentUpdate是react组件的一个生命周期。在父组件更新时,子组件会调用这个函数,若是这个函数返回true,则会从新渲染,false则不渲染。react默认返回true
二、既然默认返回true,那么若是父组件从新渲染,子组件无论props有没有变化,都会从新渲染。这样时耗费性能的一件事,解决这个问题咱们就要靠SCU优化组件何时应该渲染。
三、在优化时,咱们判断新老props是否相等,若是时基本类型的值,则直接===判断。若是是引用类型,就比较麻烦,咱们须要深度比较,lodash有一个isEqual方法,能够帮咱们比较,可是这个方法很浪费性能。因此,若是每次从新渲染都要比较相等的化,那么可能反而比直接从新渲染更慢。因此,react并无帮咱们去比较,而是把这个能力交给使用者去决定。
四、若是页面没有性能优化的需求时,咱们通常不用去刻意优化,若是稍有不慎,可能会致使bug,或者使页面更卡。若是想用SCU优化的化,建议传输props时,用基本类型,这样比如较。
shouldComponentUpdate(nextProps,nextState){
if(nextProps.name===this.props.name){
return false
}else{
return true
}
}
复制代码
首次渲染:constructor-->render-->componentDidMount,将jsx经过babel转为React.createElement函数的形式。React.createElement执行会返回一个虚拟dom,虚拟dom再经过patch函数,转为真实的dom,渲染到页面上。
组件更新:shouldComponentUpdate-->render-->componentDidUpdate,获得新的虚拟dom后,经过patch函数,将新旧进行对比:比较节点名、节点属性、子元素。而后得出最小的改动方案,再去操做更改dom。
redux是一个独立的JavaScript状态管理工具。除了react,他也能够搭配其余框架使用,如vue、angular。固然,react也能够和其余状态管理工具配合,如mobx,flux等。
另外,redux的做者为了让redux能与react更好的搭配使用,开发了react-redux。
一、action:本质上是 JavaScript 普通对象,action 内必须使用一个字符串类型的type字段来表示将要执行的动做指令。
二、reducer:必须是纯函数,接收两个参数,state和action,并返回一个对象,返回的这个对象即为最新的state。根据接受的指令(action),改变当前的状态(state),并返回最新的状态。
三、createStore:能够接收三个参数,第一个参数是reducer函数(必传),第二个参数是初始state,第三个参数是增强器。第二个参数若是是对象,就会被当成初始state,若是第二个参数是函数,就会被当成store enhancer(增强器,不是中间件)。调用函数会返回一个store对象。
四、store.getState():不须要参数,返回值是state树。
五、store.dispatch(action):改变state树的惟一途径。他会分发action,并调用reducer函数,reducer函数根据action,来改变state。他接受一个参数action。
六、store.subscribe(listener):订阅方法,当执行这个函数后,会添加一个监听器。接收一个回调函数做为参数,dispatch时,会触发回调函数。在回调函数里,咱们能够用store.getState()方法获取最新状态,判断state是否改变,从而进行一些其余操做,它会返回一个函数,执行这个函数,能够取消监听。
七、store.unsubscribe(listener):取消订阅的方法。
八、combineReducers(reducers):随着应用变得愈来愈复杂,能够考虑将 reducer 函数 拆分红多个单独的函数,拆分后的每一个函数负责独立管理 state 的一部分。在createStore的时候,咱们还要手动把多个函数合成一个函数,最终合成相似下方的函数:
function a (state,action){
return {...}
}function b (state,action){
return {...}
}
combineReducers({a,b})
// 将上面两个转化为下面一个
function root(state,action){
return {
a:a(state.a ,action),
b:b(state.b ,action)
}
}
复制代码
因此针对a 传入的都是state.a。可是假如某个场景时某个reducer须要的不是某个state,而是所有state,那么用这种写法就会出错。解决这个问题有不少思路:咱们能够把须要的数据放到action里、再添加一个reducer来适应这种特殊场景。reduce-reducers库能够简化上述过程、最笨的方法,在写reducer时,规定好特殊状况的处理方法。
九、applyMiddleware(函数或数组),接收一个参数,能够是一个middleware函数,多个middleware时,传入一个数组。它返回一个使用middleware的store enhancer。这个增强器是增强dispatch,当还有其余增强器的时候,要用compose方法链接,applymiddleware要放到compose的第一个参数。
增强器是用来增强store的功能,最多见的是增强dispatch,而增强dispatch的函数,咱们叫作中间件,applyMiddleware就是使用中间件,返回一个增强器。当咱们想要增强多个方面时,就须要多个增强器,此时就要用到compose函数,把多个增强器合成一个函数,由于createStore方法,只能接受一个增强器的参数。
中间件的本质就是替换原生的store的dispatch方法,让他在dispatch的时候不是直接到reducer,而是通过一些处理,中间件能够链接,从而实现串联。
加强器的本质其实就是加强store,store是一个对象,除了保存了一些状态,还有一些方法,如dispatch,getState,subscribe,replaceRudecer。每个函数均可以被修改。经常使用的模式就是先把原生的方法保存,而后让原来的方法赋值为新的函数,在这个新的函数里加入一些功能,每每最后仍是调用原生的方法。加强器还能够给store增长新的方法。
必定要把applyMiddleware放到最左边,compose的时候,从右到左合成,也就是最左边的是最后合成时包在最外层的,最早执行。这样作防止,一些异步操做致使其余store enhancer之间的冲突。
import {createStore,compose,applyMiddleware} from 'redux'
import CreateThunkMiddleware from 'redux-thunk'
const enhancers = compose(applyMiddleware(thunkMiddleware),xxx其余增强器)
createStore(rootReducer,enhancers)
复制代码
经过context,让每一个子组件能够获取store,而后经过connect函数,将子组件外包一层组件,在这层组件中进行state的数据处理,在经过props的方式传递给子组件
复制代码
import MyContext from './context' // 模拟实现
import React from 'react'
function connect(mapStateToProps, mapDispatchToProps) {
return function (Component) {
class HOC extends React.Component {
static contextType = MyContext
state = {}
componentDidMount() {
this.context.store.subscribe(this.upDate)
}
componentWillUnmount() {
this.context.store.unsubscribe(this.upDate)
}
upDate = () => {
this.setState({})
}
render() {
const store = this.context.store
const newProps = {
...this.props,
...mapStateToProps(store.getState(), this.props),
...mapDispatchToProps(store.getState(), this.props)
}
return <Component {...newProps} />
}
}
return HOC
}
}
复制代码
一、是一个函数。
二、接收两个参数,state和ownProps。第一个是必传的,当只传第一个参数的时候,只有当store发生变化时,会调用mapStateToProps。 当指定ownProps时,这个ownProps表明的是从父组件传入的props,此时,只要props变化,就会从新触发mapStateToProps。
一、能够是函数,也能够是对象。
二、若是传递的是一个对象,那么每一个定义在该对象的函数都将被看成 Redux action creator,对象所定义的方法名将做为属性名;每一个方法将返回一个新的函数,函数中dispatch方法会将action creator的返回值做为参数执行。这些属性会被合并到组件的 props 中。
三、若是是一个函数,能够接收两个参数,dispatch和ownProps。规则同上。返回一个对象,这个对象会和props合并
四、若是不指定,默认状况下,dispatch会注入到你的组件 props 中
在没有使用redux时,咱们请求数据都是直接在react组件里完成了,可是当状态变得复杂时,咱们就须要用到redux了,此时请求数据的过程,应该也放到redux里进行处理,获得结果后,直接塞到redux里,才是一个合理的设计,因此才有了redux异步的需求。
借助中间件 如redux-thunk、redux-saga
import CreateSagaMiddleware,{takeEvery,takeLatest} from 'redux-saga'
import {put,call,take,} from 'redux-saga/effects'
const saga = new CreateSagaMiddleware()
createStore(reducer,applyMiddleware(saga))
saga.run(root)
function *root(){
yield takeEvery(...)
}
put dispatch
call 调用函数
复制代码
页面触发一个action--->dispatch中间件处理--->reducer返回新的state--->从新渲染
一、合理使用shouldComponentUpdate
react中的shouldComponentUpdate默认返回true,根据状况合理进行优化。或者使用React.pureComponent、React.memo
SCU默认返回true,可是若是想让react进行浅比较,那就用purecomponent,这样react就会自动帮咱们进行浅比较。
class App extends React.pureComponent{}
在函数组件中,由于没有生命周期,因此react也不能帮咱们去判断,因此就用就用React.memo,
他会保存一个副本,若是判断新props不会产生影响,则返回这个缓存副本,而不会从新渲染组件
function MyComponent(props){
...
}
function likeSCU(prevProps,nextProps){
// 判断新老props是否对结果有影响,返回true或false
}
export default React.memo(MyComponent,likeSCU)
复制代码
2、合理使用mapStateToProps
react-redux至关于在组件外包一层容器组件,而这个容器组件内的shouldComponentUpdate函数,是进行了浅比较的,因此,在connect里传入的mapStateToProps函数,必定要只返回组件用到的数据,不能直接返回整个state,若是返回了整个state,那么每一次数据改变,组件都会从新渲染,浪费性能
此外,若是咱们不传mapStateToProps函数,那么就至关于不接收store里的数据,只接收从dom上传递的数据。这样作虽然没有实际意义,但也是侧面进行了优化,他会检测父组件传来的props是否有变化,若是没有变化,不会更新子组件。React.pureComponent实现了这一功能,也是进行了一次浅比较。
3、合理传递props
当咱们向子组件传递props时,尽可能不要直接传入一个对象,或匿名函数,这样作会致使每次从新渲染时,都会产生一个新的对象,即便数据没变,也会致使子组件从新渲染。
// 错误
<Child style={{color:'red'}}/>
// 正确
const fooStyle = {color:'red'}
<Child style={fooStyle}/>
复制代码
当咱们传入一个匿名函数的时候,通常都是为了传入一些参数。可是这样作就是使每一次从新渲染时,dom都会更新,致使浪费性能。若是想解决这个问题,咱们能够将dom变成一个组件。将须要用到的参数经过props传入这个组件中,调用函数时,就不用传入一个匿名函数了。
<Child onClick={()=>this.fun(item.id)}/>
<Child id={item.id} />
mapStateToProps=(dispatch,ownProps)=>{
cosnt {id} = ownProps
return {
fun:()=>{fun(id)}
}
}
复制代码
4、渲染列表时使用key,避免浪费性能,另外注意,虽然key是props传递的,可是组件内接受不到这个props,由于key和ref是react中保留的两个特殊prop。
五、 在componentwillunmount中销毁事件绑定
六、合理使用异步组件
七、不要再render里使用bind(this),由于每次从新渲染都会从新bind
八、webpack进行优化
九、前端通用的优化手段,懒加载
一、经过缓存来减小计算次数
前面说过,咱们在mapStateToProps函数里返回的必定是组件中用到的值,否则会引起组件的从新渲染,形成浪费。那么若是咱们在mapStateToProps中返回的值,须要通过一层复杂的过滤,那么岂不是每次store更新时,都会进行一次复杂的运算。没错,这样确定时浪费资源的。为了解决这个问题,咱们须要一种缓存机制,即对比新旧的数据,若是没有发生变化,咱们就直接返回上一次存储的数据。它就是:reselect!
以todo应用为例:
const selectTodos = (todos,filter)=>{
switch(){
case 'xxx':return todos.filter(item=>item.completed)
...
}
}
const mapStateToProps = (state)=>{
return {
todos:selectTodos(state.todos,state.filter)
}
}
// 使用reselect后
import {createSelector} from 'reselect'
// 调用createSelector函数后,返回一个函数,它接收state
export const reselectTodos = createSelector(
// 第一个参数是函数数组,每一个函数返回的值会进行比较,若是相同,则直接返回上一次计算结果。
[state=>state.filter,state=>state.todos],
// 若是不相同,则用函数数组中每一个函数的返回值当参数,传入第二参数函数中,从新计算。
(filter,todos)=>{
switch(){
case 'xxx':return todos.filter(item=>item.completed)
...
}
})
const mapStateToProps = (state)=>{
return {
todos:reselectTodos(state)
}
}
复制代码
二、经过合理设计redux数据结构,来减小计算复杂度
假如咱们给在传统todo项目的基础上,增长一个表示是否为紧急状态的数据。那么这时候咱们有两种设计模式。
// 反范式化
id:1,
text:'xxx',
completed:false,
type:{
name:'紧急',
color:'red'
}
// 或者,范式化
{
id:1,
text:'xxx',
completed:false,
typeId:1
}
[
{
id:1,
name:'紧急',
color:'red'
},{
id:2,
name:'还行',
color:'green'
}
]
复制代码
在项目中,咱们应该尽可能使用范式化的数据结构。由于,当咱们去更改一个项目的紧急程度所对应的颜色时,会很是方便,而若是时反范式化,则要遍历全部的对象。虽然范式化多了一次根据typeId匹配状态对象的计算过程,可是由于前面咱们用了reselect缓存,因此并不会增长性能消耗!