index

文章目录
  1. 1. React 0.14
    1. 1.1. 脏值检查
    2. 1.2. setState
    3. 1.3. 合成事件 SyntheticEvent
  2. 2. 不/受控组件
  3. 3. React 15
  4. 4. React 16
    1. 4.1. Fiber调度算法
    2. 4.2. 生命周期调整
      1. 4.2.1. 生命周期执行顺序
    3. 4.3. hooks
      1. 4.3.1. 不能在if内使用useState
      2. 4.3.2. useCallback的闭包坑
    4. 4.4. ErrorBoundary

React 0.14

脏值检查

起源于anglar1,框架定时对比state的所有值,检查出变更值,然后更新UI。而Vue和React则将该部分的工作移到了domDiff去,脏值检查变成了,脏dom检查。【参考资料

setState

注意里面利用的Promise来优化renderComponent的渲染次数,本来还以为用的是throttle;【参考资料

const queue = []; // setState的队列
const renderQueue = [];

function setState (stateChange, component) {
if (queue.length === 0) {
// 异步执行
Promise.resolve().then(flush);
}

queue.push({ stateChange, component });

if (!renderQueue.some(item => item === component)) {
renderQueue.push( component );
}
}

function flush () {
let item;
while(item = queue.shift()) {
const { stateChange, component } = item;
// 更新组件的state
Object.assign(component.state, stateChange);
}

// 遍历渲染组件
while( component = renderQueue.shift() ) {
renderComponent(component);
}
}
class App extends Component {
constructor() {
super();
this.state = { num: 0 }
}

componentDidMount () {
this.setState({ num: this.state.num + 1 });
this.setState({ num: this.state.num + 2 });
this.setState({ num: this.state.num + 3 });
}

render () {
console.log(this.state.num);
return (
...
);
}
}

// 0
// 3

合成事件 SyntheticEvent

官方文档】【参考资料

  • 在document上委托代理全局dom事件
  • 配合event.targetevent.type找到真正的触发源和触发事件类型
  • 将真正的事件映射为合成事件,比如dom的onchange映射为onChange
  • event.stopPropagation只能阻止react的事件冒泡,不能阻止真正dom事件冒泡(可以使用ReactDOM.findDOM获取真正的dom,然后addEventListener来阻止dom事件冒泡)
  • event.nativeEvent.stopImmediatePropagation 常用于阻止document的事件执行

不/受控组件

参考资料

  • 受控组件:input值是被value和onChange控制的
  • 不受控组件:input值是dom自己管理的,需要通过ref来获取当前值;
  • 全不受控组件:例如MyInput组件只提供defaultVal而不提供val;初始化后,内部的state就不可改了,可以使用key来刷新全不受控组件【参考资料
// 使用不受控组件可以写出更简单的代码(无需state、和onChange)
function MyInput () {
const inputEl = useRef(null);
const onClick = () => {
console.log(inputEl.current.value);
}

return (
<>
<input ref={inputEl} />
<button onClick={onClick}>log value</button>
</>
);
}

React 15

  • 移除data-reactid:使用document.createElement替代innerHtml来mount后可以直接得到到组件的引用并用来进一步确定是否为同一个组件 【参考资料
  • 将react-dom抽离出react库:从多平台考虑,可以减少react-native的依赖,因为native端不会用到dom的逻辑
  • 新增shouldComponentUpdate来减少组件的更新频率【更多优化技巧
    // 默认情况下一直返回true
    shouldComponentUpdate () {
    return true;
    }

    // 自定义覆盖
    shouldComponentUpdate(nextProps, nextState) {
    // 注意对象和数组不能直接用引用地址判断
    return nextState.someVal !== this.state.someVal;
    }

React 16

  • import PropTypes from ‘prop-types’;
  • React Fragment
  • Suspense & React.lazy & import()
  • Fiber
  • v16.3 生命周期调整
  • v16.8 中引入 hooks

Fiber调度算法

源码

React分三层

  • Virtual DOM
  • Reconciler: 负责调用组件生命周期方法,进行DomDiff
  • Renderer: 根据不同的平台,渲染出(DomPatch)相应的页面,比较常见的是 ReactDOM 和 ReactNative

在React16中推出,主要解决React15中Stack Reconciler占用时间过长导致掉帧的问题;结合window.requestIdleCallback和每一个节点的domDiff后会检查并执行其它优先级更高的任务,确保不阻塞动画和用户交互事件;【参考资料

  • synchronous: 与之前的Stack Reconciler操作一样,同步执行
  • task: 在nexttick之前执行
  • animation: 下一帧之前执行
  • high: 在不久的将来立即执行
  • low: 稍微延迟执行也没关系
  • offscreen: 下一次render时或scroll时才执行
const fiber = { // 类似vue的组件树
stateNode, // 节点实例
child, // 子节点
sibling, // 兄弟节点
return, // 父节点
}

生命周期调整

为了react17的Fiber Async Rendering 可打断的生命周期铺路,一旦被打断,这些被废弃的生命周期都会被多次重新执行;一般情况下,尽量少使用新的api【参考资料1】【参考资料2

  • 丢弃: componentWillMount, componentWillReceiveProps, componentWillUpdate
  • 新增: static getDerivedStateFromProps, getSnapshotBeforeUpdate
    class Example extends React.Component {

    state: {
    filterText: '',
    }

    static getDerivedStateFromProps(nextProps, prevState) {
    // static中不能使用this,避免了this.setState的副作用
    if (prevState.filterText != state.filterText)
    return {
    filterText: state.filterText
    }
    }

    getSnapshotBeforeUpdate(prevProps, prevState) {
    // 代替componentWillUpdate
    }
    }

生命周期执行顺序

init

  • constructor
  • componentWillMount
  • render
  • componentDidMount
  • componentWillUnmount

rerender

  • componentWillReceiveProps, getDerivedStateFromProps
  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate
  • componentWillUnmount

hooks

理解hooks的原理

不能在if内使用useState

let ref = {};
let index = 0;
const stateArr = [];

function useState (val) {
stateArr[index] = val;
return [ stateArr[index], setState.bind(this, index++) ];
}

function setState (index, val) {
stateArr[index] = val;
render();
}

function useRef () {
return ref;
}

useCallback的闭包坑

参考资料

function Form() {
const [text, updateText] = useState('');

const handleSubmit = useCallback(() => {
console.log(text);
// }, []); // 坑一:这样的话,由于闭包的原因,text读取的是旧值
}, [text]); // 坑二:每次text变化时handleSubmit的值都会变,导致重新渲染ExpensiveTree组件

return (
<>
<input value={text} onChange={(e) => updateText(e.target.value)} />
<!-- 很重的组件,不优化会死的那种 -->
<ExpensiveTree onSubmit={handleSubmit} />
</>
);
}
// 使用useRef和useLayoutEffect解决"坑二"
// 或者把值丢在Form外面也是可以的吧?
// 或者把state和handleSubmit抽离到reducer
function Form() {
const [text, updateText] = useState('');
const textRef = useRef();

useLayoutEffect(() => {
textRef.current = text; // 将 text 写入到 ref
});

const handleSubmit = useCallback(() => {
const currentText = textRef.current; // 从 ref 中读取 text
alert(currentText);
}, []); // handleSubmit 只会依赖 textRef 的变化。不会在 text 改变时更新

return (
<>
<input value={text} onChange={e => updateText(e.target.value)} />
<ExpensiveTree onSubmit={handleSubmit} />
</>
);
}

ErrorBoundary

因为react里会有一些异步逻辑(比如Fiber的任务打断),直接使用try&catch不能完整捕获所有的Error;

<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
// Display fallback UI
return { hasError: true };
}

componentDidCatch(error, info) {
// Display fallback UI
// this.setState({ hasError: true });

// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}

render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}