Open jYh1014 opened 3 years ago
元素是构成react项目的最小单位,是用jsx语法来声明react元素的,与浏览器的dom元素不同,这些react元素,将来会被babel经过编译的,经过编译后(其实就是React.createElement方法的一个语法糖)返回的其实就是一个普通对象(也就是虚拟dom)
<div>reactdemo</div>
上面代码会被编译成
React.createElement("div", null, "reactdemo")
虚拟dom本质上就是一个普通对象,是真实dom节点的抽象描述,将来会作为参数传入render方法,render方法会讲虚拟dom生成真实的dom节点插入到页面中。 虚拟dom的优点:如果没有虚拟dom的存在,我们以前都是操作真实的dom来达到视图的更新,这样对浏览器性能会造成很大影响。但是有了虚拟dom之后,我们只需要对这个对象做一些操作来达到视图更新的效果,具体的操作就是dom diff。
合成事件其实就是模拟原生事件(window. addEventListener),只不过在react里,他自己实现了一套逻辑,主要是对各种浏览器的兼容性做了处理,并且,合成事件和异步操作也有关系,它维护着isBatchingUpdate这个属性值,在组件更新的时候会和这个属性值的判断有很大关系。 原理:
它不是直接给目标元素绑定事件,而是委托给window对象绑定的,事件的回调函数是一个react自己实现的dispatchEvent,默认是在冒泡阶段执行,这里利用了事件代理(委托)的概念,也就是说,比如当你触发一个点击事件的时候,都会代理给window对象来执行(其实就是冒泡到window对象),这样不用给每个dom节点绑定dom事件能减少内存开销。
具体处理都在dispatchEvent这个方法里面,react会把这个函数原生的event对象创建出来一个合成对象(原来event对象的一个副本,里面做了兼容性处理),也就是说这个合成对象才是我们真正在click触发的回调函数拿到的参数。并且会收集和eventType相同的回调函数都放到一个数组里,根据是冒泡还是捕获阶段来依次执行,如果某个元素停止了冒泡,那就不再继续往下执行。
//下面我主要写一下伪代码 //首先判断属性是否事件类型 if(eventType.startWith("on")){ addEvent(vdom, eventType, value) } /** * @param {*} elem 目标元素的虚拟dom * @param {*} eventType 属性名 * @param {*} listener 属性值 */ function addEvent(elem, eventType, listener){ eventType = eventType.toLowerCase(); let eventStore = elem.eventStore || (elem.eventStore = {}); eventStore[eventType] = listener; //把事件代理给window对象,默认是冒泡阶段 window.addEventListener(eventType.substr(2), dispatchEvent, false) } function dispatchEvent(event){ //此时的event是原生的event对象 let { target, type } = event let eventType = 'on' + type; let syntheticEvent; //合成事件对象 isPending = true //设置合成事件开始处理就已经是批量更新状态 //然后执行目标元素的listener并且收集和目标元素相同事件类型的元素节点的listener while(target){ let { eventStore } = target let listener = eventStore && eventStore[eventType]; if(listener){ if (!syntheticEvent) { syntheticEvent = createSyntheticEvent(event); } syntheticEvent.currentTarget = target; //执行目标元素的listener,传入的是合成事件对象 listener.call(target, syntheticEvent) } //向上找父元素,执行listener,若父元素阻止了冒泡,那就停止循环。 target = target.parentNode; //在合成事件执行结束完成后,设置isPending为false,表示不是批量更新状态 isPending = false //组件执行更新逻辑,包括计算状态和更新视图 updateQueue.batchUpdate(); } //销毁合成事件对象,把对象的属性都设置为null for(let key in syntheticEvent){ //value = null } function createSyntheticEvent(event){ //拷贝了原生event事件的属性,并且修改了内部this指向为合成事件对象 let syntheticEvent = {} syntheticEvent.nativeEvent = #nativeEvent for (let key in nativeEvent) { if (typeof nativeEvent[key] == 'function') { syntheticEvent[key] = nativeEvent[key].bind(nativeEvent) } else { syntheticEvent[key] = nativeEvent[key] } } //此处还做了兼容性处理,重写了原生了stoppropagation方法等等 return syntheticEvent }
//声明一个缓存队列 let updateQueue = { updaters: [], isPending: false, add(updater){ updaters.push(updater) }, batchUpdate(){ if (this.isPending) { return; } this.isPending = true; let { updaters } = this; let updater; //依次执行缓存的updater中的updateComponent方法来更新视图 while ((updater = updaters.pop())) { updater.updateComponent(); } this.isPending = false; } } class Component{ constructor(props){ this.props = props this.state = {} this.$updater = new Updater(this) } setState(partialState){ this.$updater.addState(partialState) } forceUpdate() { let { props, state, renderElement: oldRenderElement } = this; if (this.componentWillUpdate) { this.componentWillUpdate(props, state); } let newRenderElement = this.render(); let currentElement = compareTwoElements(oldRenderElement, newRenderElement); this.renderElement = currentElement; if (this.componentDidUpdate) { this.componentDidUpdate(props, state); } } } class Updater { constructor(instance){ this.instance = instance //组件实例 this.pendingStates = [] //缓存的状态数组 this.nextProps = null; } addState(partialState){ this.pendingStates.push(pendingStates) this.emitUpdate(); } emitUpdate(nextProps){ this.nextProps = nextProps; if(!updateQueue.isPending){ this.updateComponent() }else {updateQueue.add(this)} } updateComponent(){ //计算最新的状态,然后执行生命周期 shouldUpdate( instance, nextProps, this.getState() ); } getState(){ // let { instance, pendingStates } = this; let { state } = instance; pendingStates.forEach(nextState => { if(typeof nextState == "function"){ //如果传入的函数的话,则执行,并且函数的参数是上一次的状态 nextState.call(instance, state) }else{ //传入对象的状态都会进行合并 state = {...state, ...nextState} } return nextState }) } } function shouldUpdate(component, nextProps, nextState) { component.props = nextProps; component.state = nextState; if (component.shouldComponentUpdate && !component.shouldComponentUpdate(nextProps, nextState)) { return; } component.forceUpdate(); }
总结一下过程:setState ——> 执行Component实例的setState ——> updater实例的addState ——> 把状态缓存到数组里,判断若是处于批量更新状态,则把这个updater缓存到对列里,若不是处于批量更新 ——> updateComponent ——> 计算最新状态 ——> shouldComponentUpdate若返回true ——> willupdate ——> 调用组件实例的render ——> domdiff更新视图
是用来访问组件实例或者真实dom元素的,通常用于父组件想要访问子组件的某些属性或者方法来做一些操作。
用法:
//字符串方式 <input ref = "a" onFocus=.../> //就可以通过this.refs.a来获取到input节点,this.refs.a.onFocus就是input的focus事件
//函数方式 <input ref ={ inst=>this.textInput = inst } /> //this.textInput就是inputdom节点
//createRef方式,dom元素指向的createRef方法返回值的current属性 class Parent extends Component{ constructor(){ this.textInput = React.createRef() } handleClick=()=>{ console.log(this.textInput.current.onfocus) } render(){ return ( <div> <input ref = {this.textInput} onfocus=... /> <button onClick={this.handleClick}>点击</button> </div> ) } } //简单写一下其原理 function createRef(){ return { current: null } }
以上三个方法都不适用于函数组件,因为函数组件内部没有this即没有实例,也就是说不能直接给函数组件写ref属性,当然,函数组件内部可以使用useRef
function App(){ } <App ref=.../> //这种方式是错误的
//我们想在父组件拿到子函数组件的方法,可以先把函数组件转化为class组件,通过forwardRef function TextInput(props,ref){ return <input ref={ref}/> } const ForwardTextInput = React.forwardRef(TextInput) class App { getFocus = () => { this.textInput.current.focus(); this.textInput.current.value = "focus" } render(){ <div> <ForwardTextInput ref={this.textInput} /> <button onClick={this.getFocus}>获取焦点</button> </div> } }
//简单实现一下forwardRef function forwardRef(FunctionComponent){ //返回的是一个class组件 return class extends React.Component{ render(){ return FunctionComponent(this.props,this.props.ref) } } }
主要是为了解决父子组件传递属性的问题,比如,有三个组件从父到子的关系是Page——>Header——>Title,Page组件想要给Title组件传递值,可是我们又不想先传递给Header组件,然后由Header组件传递给Title,context就是为了解决这个问题,可以跨组件访问属性
基本用法如下
let ThemeContext = React.createContext() //{Provide,Comsumer} class Page extends React.Component { constructor(props) { super(props); this.state = { color: "red" } } render() { let value = { color: this.state.color} return ( <ThemeContext.Provider value={value}> <div > page <Header /> </div> </ThemeContext.Provider> ) } } //header组件 class Header extends React.Component { constructor(props) { super(props); } static contextType = ThemeContext //组件实例上会多一个属性this.context = Provide.value render() { return ( <div style={{ margin: "10px", border: `5px solid ${this.context.color}`, padding: "5px" }}>Header <Title /> </div> ) } } //Title组件 function Title(props) { return ( <ThemeContext.Consumer> { value => ( <div style={{ margin: "10px", border: `5px solid ${value.color}`, padding: "5px" }}>Title </div> ) } </ThemeContext.Consumer> ) }
//简单实现一下context function createContext(){ function Provider(props){ Provider.value = props.value } function Consumer(props){ props.children(Provider.value) } return {Provider, Consumer} }
本质上是一个函数,参数是一个组件并且返回一个组件
用处:属性代理(主要是为了复用代码,抽离公共逻辑,加强原组建也就是给传入的组件传入一些公共属性或者状态)
渲染劫持(改变原组件的渲染输通过反向代理原组件,根据不同条件super.render())
经过高阶组件包装后会造成原组件静态方法丢失,所以要注意拷贝
ref不能传递属性,要用到React.forward方法
//父组件是函数组件 <div> <Child render={({a,b})=>(<div>a</div>)}/> </div> //child组件 <div > {this.props.render(this.state)} </div>
<ul> <li key="0">1</li> <li key="1">2</li> <li key="2">3</li> <li key="3">4</li> </ul> //新的 <ul> <li key="0">1</li> <li key="1">5</li> <li key="2">2</li> <li key="3">3</li> <li key="4">4</li> </ul>
class PureComponent extends Component{ shouldComponentUpdate(nextProps,nextState){ console.log(nextProps===this.props) if(!shallowEqual(this.props,nextProps)){ return true } return false } } function shallowEqual(obj1, obj2) { if (obj1 === obj2) { return true } // debugger let noObj1 = typeof obj1 !== 'object' || obj1 === null let noObj2 = typeof obj2 !== 'object' || obj2 === null if (noObj1 || noObj2) { return false } let keys1 = Object.keys(obj1) let keys2 = Object.keys(obj2) if (keys1.length !== keys2.length) { return false } for (const key of keys1) { let equalKey = obj1[key] === obj2[key] if (!obj2.hasOwnProperty(key) || !equalKey) { return false } } return true }
react相关(15版本)
1.什么是react元素?
元素是构成react项目的最小单位,是用jsx语法来声明react元素的,与浏览器的dom元素不同,这些react元素,将来会被babel经过编译的,经过编译后(其实就是React.createElement方法的一个语法糖)返回的其实就是一个普通对象(也就是虚拟dom)
上面代码会被编译成
2.什么是虚拟dom?
虚拟dom本质上就是一个普通对象,是真实dom节点的抽象描述,将来会作为参数传入render方法,render方法会讲虚拟dom生成真实的dom节点插入到页面中。 虚拟dom的优点:如果没有虚拟dom的存在,我们以前都是操作真实的dom来达到视图的更新,这样对浏览器性能会造成很大影响。但是有了虚拟dom之后,我们只需要对这个对象做一些操作来达到视图更新的效果,具体的操作就是dom diff。
3.什么是合成事件?
合成事件其实就是模拟原生事件(window. addEventListener),只不过在react里,他自己实现了一套逻辑,主要是对各种浏览器的兼容性做了处理,并且,合成事件和异步操作也有关系,它维护着isBatchingUpdate这个属性值,在组件更新的时候会和这个属性值的判断有很大关系。 原理:
它不是直接给目标元素绑定事件,而是委托给window对象绑定的,事件的回调函数是一个react自己实现的dispatchEvent,默认是在冒泡阶段执行,这里利用了事件代理(委托)的概念,也就是说,比如当你触发一个点击事件的时候,都会代理给window对象来执行(其实就是冒泡到window对象),这样不用给每个dom节点绑定dom事件能减少内存开销。
具体处理都在dispatchEvent这个方法里面,react会把这个函数原生的event对象创建出来一个合成对象(原来event对象的一个副本,里面做了兼容性处理),也就是说这个合成对象才是我们真正在click触发的回调函数拿到的参数。并且会收集和eventType相同的回调函数都放到一个数组里,根据是冒泡还是捕获阶段来依次执行,如果某个元素停止了冒泡,那就不再继续往下执行。
4.setState都发生了什么
总结一下过程:setState ——> 执行Component实例的setState ——> updater实例的addState ——> 把状态缓存到数组里,判断若是处于批量更新状态,则把这个updater缓存到对列里,若不是处于批量更新 ——> updateComponent ——> 计算最新状态 ——> shouldComponentUpdate若返回true ——> willupdate ——> 调用组件实例的render ——> domdiff更新视图
5.函数组件
6.refs
是用来访问组件实例或者真实dom元素的,通常用于父组件想要访问子组件的某些属性或者方法来做一些操作。
用法:
以上三个方法都不适用于函数组件,因为函数组件内部没有this即没有实例,也就是说不能直接给函数组件写ref属性,当然,函数组件内部可以使用useRef
7.context
主要是为了解决父子组件传递属性的问题,比如,有三个组件从父到子的关系是Page——>Header——>Title,Page组件想要给Title组件传递值,可是我们又不想先传递给Header组件,然后由Header组件传递给Title,context就是为了解决这个问题,可以跨组件访问属性
基本用法如下
8.高阶组件
本质上是一个函数,参数是一个组件并且返回一个组件
用处:属性代理(主要是为了复用代码,抽离公共逻辑,加强原组建也就是给传入的组件传入一些公共属性或者状态)
渲染劫持(改变原组件的渲染输通过反向代理原组件,根据不同条件super.render())
经过高阶组件包装后会造成原组件静态方法丢失,所以要注意拷贝
ref不能传递属性,要用到React.forward方法
9.render props
9.domdiff
性能优化