React 学习笔记 - 1 生命周期和数据对象

React给我的第一感受就是封装至上,他不喜欢mixin更偏爱用层层封装,好处显而易见,多种多样的嵌套可以灵活的组合出很多不同的组件,如果说js万物皆对象的话,那react就是全部是组件。
和Vue不同

直接先用官方脚手架初始化一个项目,后期再照猫画虎学习配置项

npm init react-app my-app
// yarn create react-app my-app

想要看配置项可以在项目目录

npm run rject  //导出配置项 *不可逆

React使用jsx语法作渲染,说白了就是可以让js直接返回不需要加引号的html元素。
在项目目录的public文件夹里index.html中存在id为root的根标签,和vue里做的一样,index.js文件里面,引入ReactDom,用它的render方法把一个根组件渲染到这个节点中。

import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

App里面使用class方法定义了一个类,这个类就是App组件,它继承了React.Component,写组件的实际也是重载一下Component类的方法,借助编辑器可以进到它的d.ts文件里,看一看定义

    interface Component<P = {}, S = {}, SS = any> extends ComponentLifecycle<P, S, SS> { }
    class Component<P, S> {
        constructor(props: Readonly<P>);
        /**
         * @deprecated
         * https://reactjs.org/docs/legacy-context.html
         */
        constructor(props: P, context?: any);

        // We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
        // See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
        // Also, the ` | S` allows intellisense to not be dumbisense
        setState<K extends keyof S>( 
            state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
            callback?: () => void
        ): void;

        forceUpdate(callBack?: () => void): void;
        render(): ReactNode;

        // React.Props<T> is now deprecated, which means that the `children`
        // property is not available on `P` by default, even though you can
        // always pass children as variadic arguments to `createElement`.
        // In the future, if we can define its call signature conditionally
        // on the existence of `children` in `P`, then we should remove this.
        readonly props: Readonly<{ children?: ReactNode }> & Readonly<P>;
        state: Readonly<S>;
        /**
         * @deprecated
         * https://reactjs.org/docs/legacy-context.html
         */
        context: any;
        /**
         * @deprecated
         * https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs
         */
        refs: {
            [key: string]: ReactInstance
        };
    }

上面构造函数接受一个props和上下文对象,一个state,一个只读的props,有一个setState方法,接受两个函数,一个是有传参preState,props的操作函数,返回值会操作更改state。第二个接受一个回调方法,想当然就是更改完成后的回调,有点像vue的nextTick。render函数就是渲染的地方了,它需要返回一个jsx。一个refs管理组件内部声明的ref。还有一个forceUpdate方法,问过谷哥以后知道这是一个强制渲染的函数,调用它就会跳过shouldComponentUpdate,在state发生变化(调用setState)或Props发生变化之后,会需要重新渲染也就是重新调用render函数,在这之前react会先问过shouldComponentUpdate这个东西,它说true,就调用render更新,它说false,那react就不管了。但是可能有一些数据需要依赖于外部其他东西,不一定是state或者props,为了在这些数据变化之后重新渲染,就需要调用forceUpdate强制render,就好像是你叫来了谷哥吓得S哥不敢吭声乖乖让开。
最上面Component还继承了ComponentLifecycle这个类,里面是这样的

    interface ComponentLifecycle<P, S, SS = any> extends NewLifecycle<P, S, SS>, DeprecatedLifecycle<P, S> {
        /**
         * Called immediately after a component is mounted. Setting state here will trigger re-rendering.
         */
        componentDidMount?(): void;
        /**
         * Called to determine whether the change in props and state should trigger a re-render.
         *
         * `Component` always returns true.
         * `PureComponent` implements a shallow comparison on props and state and returns true if any
         * props or states have changed.
         *
         * If false is returned, `Component#render`, `componentWillUpdate`
         * and `componentDidUpdate` will not be called.
         */
        shouldComponentUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: any): boolean;
        /**
         * Called immediately before a component is destroyed. Perform any necessary cleanup in this method, such as
         * cancelled network requests, or cleaning up any DOM elements created in `componentDidMount`.
         */
        componentWillUnmount?(): void;
        /**
         * Catches exceptions generated in descendant components. Unhandled exceptions will cause
         * the entire component tree to unmount.
         */
        componentDidCatch?(error: Error, errorInfo: ErrorInfo): void;
    }

之后又继承了两个接口,一口气看完

interface NewLifecycle<P, S, SS> {
        /**
         * Runs before React applies the result of `render` to the document, and
         * returns an object to be given to componentDidUpdate. Useful for saving
         * things such as scroll position before `render` causes changes to it.
         *
         * Note: the presence of getSnapshotBeforeUpdate prevents any of the deprecated
         * lifecycle events from running.
         */
        getSnapshotBeforeUpdate?(prevProps: Readonly<P>, prevState: Readonly<S>): SS | null;
        /**
         * Called immediately after updating occurs. Not called for the initial render.
         *
         * The snapshot is only present if getSnapshotBeforeUpdate is present and returns non-null.
         */
        componentDidUpdate?(prevProps: Readonly<P>, prevState: Readonly<S>, snapshot?: SS): void;
    }

    interface DeprecatedLifecycle<P, S> {
        /**
         * Called immediately before mounting occurs, and before `Component#render`.
         * Avoid introducing any side-effects or subscriptions in this method.
         *
         * Note: the presence of getSnapshotBeforeUpdate or getDerivedStateFromProps
         * prevents this from being invoked.
         *
         * @deprecated 16.3, use componentDidMount or the constructor instead; will stop working in React 17
         * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#initializing-state
         * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#gradual-migration-path
         */
        componentWillMount?(): void;
        /**
         * Called immediately before mounting occurs, and before `Component#render`.
         * Avoid introducing any side-effects or subscriptions in this method.
         *
         * This method will not stop working in React 17.
         *
         * Note: the presence of getSnapshotBeforeUpdate or getDerivedStateFromProps
         * prevents this from being invoked.
         *
         * @deprecated 16.3, use componentDidMount or the constructor instead
         * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#initializing-state
         * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#gradual-migration-path
         */
        UNSAFE_componentWillMount?(): void;
        /**
         * Called when the component may be receiving new props.
         * React may call this even if props have not changed, so be sure to compare new and existing
         * props if you only want to handle changes.
         *
         * Calling `Component#setState` generally does not trigger this method.
         *
         * Note: the presence of getSnapshotBeforeUpdate or getDerivedStateFromProps
         * prevents this from being invoked.
         *
         * @deprecated 16.3, use static getDerivedStateFromProps instead; will stop working in React 17
         * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#updating-state-based-on-props
         * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#gradual-migration-path
         */
        componentWillReceiveProps?(nextProps: Readonly<P>, nextContext: any): void;
        /**
         * Called when the component may be receiving new props.
         * React may call this even if props have not changed, so be sure to compare new and existing
         * props if you only want to handle changes.
         *
         * Calling `Component#setState` generally does not trigger this method.
         *
         * This method will not stop working in React 17.
         *
         * Note: the presence of getSnapshotBeforeUpdate or getDerivedStateFromProps
         * prevents this from being invoked.
         *
         * @deprecated 16.3, use static getDerivedStateFromProps instead
         * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#updating-state-based-on-props
         * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#gradual-migration-path
         */
        UNSAFE_componentWillReceiveProps?(nextProps: Readonly<P>, nextContext: any): void;
        /**
         * Called immediately before rendering when new props or state is received. Not called for the initial render.
         *
         * Note: You cannot call `Component#setState` here.
         *
         * Note: the presence of getSnapshotBeforeUpdate or getDerivedStateFromProps
         * prevents this from being invoked.
         *
         * @deprecated 16.3, use getSnapshotBeforeUpdate instead; will stop working in React 17
         * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#reading-dom-properties-before-an-update
         * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#gradual-migration-path
         */
        componentWillUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: any): void;
        /**
         * Called immediately before rendering when new props or state is received. Not called for the initial render.
         *
         * Note: You cannot call `Component#setState` here.
         *
         * This method will not stop working in React 17.
         *
         * Note: the presence of getSnapshotBeforeUpdate or getDerivedStateFromProps
         * prevents this from being invoked.
         *
         * @deprecated 16.3, use getSnapshotBeforeUpdate instead
         * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#reading-dom-properties-before-an-update
         * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#gradual-migration-path
         */
        UNSAFE_componentWillUpdate?(nextProps: Readonly<P>, nextState: Readonly<S>, nextContext: any): void;
    }

下面这个虽然没有直接声明,但是通过ComponentClass继承了

    interface ComponentClass<P = {}, S = ComponentState> extends StaticLifecycle<P, S>
    interface StaticLifecycle<P, S> {
        getDerivedStateFromProps?: GetDerivedStateFromProps<P, S>;
    }

上面这仨东西定义了组件的生命周期,还有刚刚说的 shouldComponentUpdate
按照执行顺序理一下 #带will的表示之前,类似before,did的表示之后类似vue里的 xxed

//初次挂载阶段
1,首先肯定没悬念的第一个调用constructor构造函数
2,第二个调用的就是 getDerivedStateFromProps 这个是一个新api,在初始化实例(~父组件重新渲染),和props或者state改变时调用(别的文里说setState不会调用这个,但是实际测试后发现会被调用),接收两个值,一个是新的props,一个是目前的state,可以返回一个值,这个值作用和setState的一个值一样会改变state  
3,调用UNSAFE_componentWillMount 看名字就被冠上了不安全,作用是挂载之前调用类似VuebeforeMount,凡事被声明UNSAFE的都不能和getDerivedStateFromProps一起调用,会抛错
4,render 这时候渲染了 第一次render并不需要经过shouldComponentUpdate
5,componentDidMount 挂载完成了
//更新阶段
1,getDerivedStateFromProps 上面说过 发生了改变就会调用,第一个
2,shouldComponentUpdate第二个,先问过这个家伙之后再决定到底改不改
3,UNSAFE_componentWillUpdate 更新之前被触发,但是写了不安全
4,render 调用渲染
5,getSnapshotBeforeUpdate render完之后只是拿到了渲染结果,还没有被挂载到dom上呢,先要经过它来生成一下改之前的快照,它返回的值是下一个家伙的第三个参数
6,componentDidUpdate 更新完成啦 接受三个值,之前的props,之前的state,上面那个家伙的返回值
//销毁阶段
1,componentWillUnmount 在销毁之前调用,想注销点啥东西就在这吧
//异常错误阶段
1,componentDidCatch 会捕获派生组件里面产生的异常,接受一个Error,一个具体的Info

以上是一个组件的全部生命周期,新的API不能和旧的不安全的一起调用。

以上得知React的组件可以接受一个只被它只读的props属性,这里和Vue不同的是 Vue提供了v-on v-bind 分别来绑定props和事件,而react里面无论是数据还是事件回调都需要包裹在props里面传入。
而它的数据监听主要是依赖于state这个对象,而且如果需要更改只能调用setState来进行改变,不可以直接操纵它。
值得注意的是

componentDidUpdate() {
    console.log("componentDidUpdate", "更新之后");
    this.setState({
      number: 1
    });
  }

它并不会对你的State做检查,只要你触发了setState,他就会重新渲染就会触发回调,后果就是一直重复渲染直到报错为止。如果你想要在这里做一些优化,就需要上面的shouldComponentUpdate周期里面手动检查返回false,不过也要避免在这种生命周期中触发更改操作。
一个组件如果你没有声明constructor的话会自动调用父级构造,也不会出现一些错误,但是如果覆盖重载了的话,需要在第一行主动 super(props) 调用父级构造函数,以便做出数据绑定操作

在Vue里插值需要定义两个大括号,而在react里只需要一个,比如

render(){
    return (
        <div onClick={(e)=>this.onClick(e)}>
            {this.state.name}
        </div>
    )
}

因为是使用了类而不是模版解析,所以使用值的时候还是需要显式的声明this,同时react对原生事件做了更深的处理,使用箭头函数是因为如果直接onClick={this.onClick}的话,会导致this的作用域发生变化,被调用时函数内的this不再指向这个类本身。不只是箭头函数还可以在构造函数里,使用bind对某些函数做强制this绑定。this.onClick=this.onClick.bind(this)这样的话就可以直接调用了,不用担心this丢失的问题(类外声明一个self在某个生命周期指定为this这种事当然也是可以的)

值得一提的是React的事件统一的处理,你在组件定义的事件实际上都会被加到一个回调里,真正的点击事件只发生在顶层的document节点,也就是使用了事件委托的方法对事件做统一处理,组件触发的顺序也是冒泡的 子->父,但是这样也产生了一个问题就是这个是它自己模拟的冒泡,所以你没办法通过preventDefault来阻止原生事件,(原生事件可以通过阻止事件传播stopPropagation来阻止模拟事件)但是你还是可以在回调中使用stopPropagation来阻止模拟的事件的继续冒泡。

除了props和state,组件还可以接受一个上下文,由父级声明传入,子组件需要的话就获取,这东西主要依赖于 {createContext} from ‘react’ 这个东西类似于Vue的provide / inject 由父组件向下传递,自组件按需接收,使用的步骤如下

//context.js
import {createContext} from 'react' 
export const AppContext = createContext({
    //这里声明上下文中的对象 不写直接空对象,到时候父组件使用的时候手动传也可以,不会报错,但是...
})

//app.js
import {AppContext} from './context'
class App extends React.Component{
    render(){
        return (
          <AppContext.Provider value={{
              name:'uzi',
              onClick:()=>{console.log('Super Carry')}
          }}>
            <ChildComponent />
          </AppContext.Provider>
        )
    }
}
//child.js
import {AppContext} from './context'
class Child extends React.Component{
    render(){
        return (
          <AppContext.Consumer>
            {
                value=>{
                    return (
                        <p onClick={value.onClick}>{value.name}</p>
                    )
                }
            }
          </AppContext.Consumer>
        )
    }
}

创建好的AppContext提供了两个组件,一个Provider一个Cursumer,父级是P,子需要的话用C获取,P接受一个value作为向下传递的值,C的内部可以使用插值语法拿到value。
组件内的数据操作差不多也就这几个,当然还可以用redux等很多方式去共享数据。

最后,在无论你的组件是一个直接返回jsx的函数还是一个继承了Component的类,都可以在父组件上引入后,直接当成组件使用,而不用前置声明(vue的components)。

猜你喜欢

转载自blog.csdn.net/weixin_42345308/article/details/82224331