前端知识梳理(进阶)

React 部分

React 生命周期

React v16.3 前的生命周期

constructor
挂载阶段
componentWillMount
componentDidMount
运行阶段
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
卸载阶段
componentWillUnmount

React v16.3 后的生命周期

在之前的生命周期中,componentWillMount, componentWillReceiveProps, componentWillUpdate这个三个生命周期被抛弃,被认为是不安全的方法,为了弥补丢弃这些生命周期,新增加了两个生命周期

static getDerivedStateFromProps

  • 触发时间(v16.4修正):组件每次被render的时候,包括在组件构建之后(render之前执行),每次获取新的props或state之后。在v16.3版本时,组件state的更新不会触发该生命周期。
  • 每次接收新的props之后都会返回一个对象作为新的state,返回null则说明不需要更新state.
  • 配合componentDidUpdate,可以覆盖componentWillReceiveProps的所有用法

getSnapshotBeforeUpdate

  • 触发时间: update发生的时候,在render之后,在组件dom渲染之前。
  • 返回一个值,作为componentDidUpdate的第三个参数。
  • 配合componentDidUpdate, 可以覆盖componentWillUpdate的所有用法。

参考: https://juejin.im/post/5ae6cd96f265da0b9c106931#heading-10

React diff算法

我们都知道,像React 和 vue这种框架,都是利用js对象模拟虚拟dom,利用diff算法来记录老树与新树间的差异,最终更新真实dom,渲染新的视图。可是diff算法你又了解多少呢?

在这将它归为3类:

Tree Diff

概念: 将新旧两颗虚拟 DOM 树,按照层级对应的关系,从头到尾的遍历一遍,,就能找到那些元素是需要更新的,这种方式: Tree Diff

  1. 只会对相同颜色方框内(同级)的DOM节点进行比较,即同一父节点下的所有子节点
  2. 当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较


执行过程:create A -> create B -> create C -> delete A

Component Diff

不同组件之间的对比 概念: 在对比每一个层级的时候,会有自己的组件,这种组件的对比方式就叫: Component Diff ;

  1. 如果类型相同,暂时不更新
  2. 如果类型不相同,就需要更新; ( 删除旧的组件,再创建一个新的组件,插入到删除组件的那个位置)

执行过程:delete D -> create G

Element Diff

同一层级中元素之间的对比
概念: 在类型相同的组件内, 再继续对比组件内部的元素,查看内部元素是否相同,如果需要修改,找到需要修改的元素,进行针对性的修改! 这种方式就叫: Element Diff

总的来说diff算法就是先比较树的结构,然后比较同一位置组件的类型,最后再比较同一组件里面元素的差别三个步骤,实现dom树的更新。

React Hook 小小窥视

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

在讲Hook之前,我先来谈谈前端逻辑代码的复用做过的努力与尝试。

Mixin (混入方式)

在React 早期,是允许这种方式的存在的,这种方式类似于一种插拔的方式,哪里需要就插到哪里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var LogMixin = {
log: function() {
console.log('log');
},
componentDidMount: function() {
console.log('in');
},
componentWillUnmount: function() {
console.log('out');
}
};

var User = React.createClass({
mixins: [LogMixin],
render: function() {
return (<div>...</div>)
}
});

var Goods = React.createClass({
mixins: [LogMixin],
render: function() {
return (<div>...</div>)
}
});

然后这种方式存在以下这些问题:

  • Mixin 可能会相互依赖,相互耦合,不利于代码维护
  • 不同的Mixin中的方法可能会相互冲突
  • Mixin非常多时,组件是可以感知到的,甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性
    这样当Mixin一旦多起来,代码规范性,可维护性,以及可靠性都没法保障,很不合理。

Hoc (高阶组件)

出于对上面混入方式的不满意,React 重新提供了一种解决方案,也就是高阶组件。

何为高阶组件?高阶组件即是通过子组件,将复用逻辑通过属性代理或者反向继承的方式传个嵌套的组件,返回一个新的组件,相对于混入方式,这个是嵌套式的,不存在代码相互耦合,相互依赖的情况。在原理上要进步的很多,起码保证了代码的安全性,可靠性。

1
2
3
4
5
6
7
8
/** 属性代理 */
function proxyHOC(WrappedComponent) {
return class extends Component {
render() {
return <WrappedComponent {...this.props} />;
}
}
}

1
2
3
4
5
6
7
8
/** 反向继承 */
function inheritHOC(WrappedComponent) {
return class extends WrappedComponent {
render() {
return super.render();
}
}
}

Hoc 的缺点:

  • HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套,这让调试变得非常困难。
  • HOC可以劫持props,在不遵守约定的情况下也可能造成冲突,比如Props覆盖。

Hook 方式。

Hooks是React v16.7.0-alpha中加入的新特性。它可以让你在class以外使用state和其他React特性。
官方提供的Hook:

  • State Hook (相当于操作state)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    export default function HookTest() {
    const [count, setCount] = useState(0);
    return (
    <div>
    <p>You clicked {count} times</p>
    <button onClick={() => { setCount(count + 1); setNumber(number + 1); }}>
    Click me
    </button>
    </div>
    );
    }
  • Effect Hook (相当于操作effect副操作)

    1
    2
    3
    4
    5
    6
    useEffect(() => {
    // 只要组件render后就会执行
    });
    useEffect(() => {
    // 只有count改变时才会执行
    },[count]);
  • Ref Hook

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    使用useRef Hook,你可以轻松的获取到dom的ref。

    export default function Input() {
    const inputEl = useRef(null);
    const onButtonClick = () => {
    inputEl.current.focus();
    };
    return (
    <div>
    <input ref={inputEl} type="text" />
    <button onClick={onButtonClick}>Focus the input</button>
    </div>
    );
    }
  • 自定义Hook

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /** 双向绑定 */
    function useBind(init) {
    let [value, setValue] = useState(init);
    let onChange = useCallback(function(event) {
    setValue(event.currentTarget.value);
    }, []);
    return {
    value,
    onChange
    };
    }

    function Page1(props){
    let value = useBind('');
    return <input {...value} />;
    }
  1. 减少逻辑复用的风险:
    Hook 的方式与Minin 方式其实很像,但是Mixin引入的逻辑和状态是可以相互覆盖的,而多个Hook之间互不影响。
    在不遵守约定的情况下使用HOC也有可能带来一定冲突,比如props覆盖等等,使用Hook则可以避免这些问题。

  2. 避免地狱式嵌套
    大量使用HOC的情况下让我们的代码变得嵌套层级非常深,使用Hook,我们可以实现扁平式的状态逻辑复用,而避免了大量的组件嵌套,便于管理。

  3. 让组件更利于理解
    在使用class组件构建我们的程序时,他们各自拥有自己的状态,业务逻辑的复杂使这些组件变得越来越庞大,各个生命周期中会调用越来越多的逻辑,越来越难以维护。使用Hook,可以让你更大限度的将公用逻辑抽离,将一个组件分割成更小的函数,而不是强制基于生命周期方法进行分割。

总结: Hook的出现不是为了代替Class来的,以我的拙见,在代码的复用上,和扩展性上才是Hook出现的初衷,在未来的开发中我们可以去尝试使用Hook,让更多公用的功能被Hook化,让我们的开发量更小,让代码看起来不那么混乱难以理解。当然Hook这个出现时间还不长,肯定会有很多问题会在将来暴露出来,在实践中不断前行。

坚持原创技术分享,您的支持将鼓励我继续创作!