React

React

demo 地址
React 的世界里一切皆是组件,我们使用 class 语法构建一个最基本的组件,组件的使用方式和 HTML 相同,组件的 render 函数返回页面渲染的一个 JSX,然后使用 ReactDom 渲染到页面里

React 和 Vue 的对比

组件化方面

  1. 模块化: 从代码的角度进行分析,把一些可复用的代码抽离成单个模块,便于项目维护和开发;如 Node
  2. 组件化:从 UI 界面角度进行分析,把一些可复用的 UI 元素,抽离为单独的组件。
  3. Vue 如何实现组件化:.vue文件
    • template 结构
    • script 行为
    • style 样式
  4. React 如何实现组件化:一切以 js 来表现。ES6、ES7(async 和 await…)

移动 APP 开发体验方面

  • VUE 是 结合 Week(阿里)
  • React, 结合 ReactNative

几个核心概念

  1. 虚拟 DOM

    为了实现 DOM 元素的高效更新,性能问题,频繁操作 DOM

    需要按需渲染

    获取内存中的新旧 DOM 树,对比,按需更新 DOM,浏览器中并无直接提供获取 DOM 树的 API,因此 用 js 对象模拟新旧 DOM 树的虚拟 DOM 出现了。

  2. DIFF 算法

    新旧 DOM 树的对比

    • tree diff: 新旧两颗 DOM 树,逐层对比的过程
    • Component diff:在进行 tree diff 过程中,每一层中 组件级别的对比。
    • Element diff :在进行 component diff 过程中,如果两个组件类型相同,则进行元素级别的对比。

实践步骤:

webpack4.x 和 3 的简述差别

约定大于配置,如 约定的入口路径是 src -> index.js,不用再写 entry 字段

新增 mode 值

项目实践流程

  1. npm init -y

  2. 建目录 src 和 dist

  3. 安装 webpack4 及相关

    npm i webpack webpack-cli -D

    npm i webpack-dev-server -D

  4. 新建webpack.config.js,配置

  1. 安装 react

    npm i react react-dom -S

    react 包是专门用来创建组件和 js 虚拟 DOM 的,同时组件的生命周期都在这个包里

    React-dom 是 专门进行 DOM 渲染和操作的,最主要的应用场景是ReactDOM.render()

  2. index.html中 创建容器以供入口 js 调用 render

    <div id="app"></div>
    
  3. 导入包 在入口文件index.js

    import React from "react";
    import ReactDom from "react-dom";
    
    // 创建虚拟DOM元素
    const myDiv = React.createElement(
      "div",
      { id: "xx", style: { display: "block" } },
      "哈哈哈",
      React.createElement("p", {}, "2")
    );
    
    // const myDiv = <div id="d1">第一个div元素</div>;
    
    // 使用ReactDom 吧虚拟DOM渲染到页面上
    // 参数1 要渲染的那个虚拟DOM元素
    // 参数2 指定页面上的一个DOM容器
    ReactDom.render(myDiv, document.getElementById("app"));
    
  1. 安装babel插件

    npm i babel-core babel-loader babel-plugin-transform-runtime -D

    语法包 npm i babel-preset-env babel-preset-stage-0 -D

    能够识别转换 jsx 的语法包npm i babel-preset-react -D

  2. Webpack.config.js中配置 babel-loader

  3. 配置.babelrc配置文件

"presets": ["env","stage-0","react"],
"plugins": []

JSX 语法

  • jsx 语法的本质:并不是直接把 jsx 渲染到页面上,而是内部先转换成了 createElement 形式,再渲染的

  • jsx 中混合写入 js 表达式:在 jsx 语法中把 js 代码写在{}

    包括:渲染数字、字符串、布尔值、为属性绑定值、渲染 jsx 元素、渲染 jsx 元素数组、将普通字符数组转为 jsx 数组并渲染到页面上

  • 注释

  • 标签必须成堆出现,如果是单标签,则必须自闭合

  • 在 jsx 中创建 DOM 的时候,所有的节点,必须有唯一的跟元素进行包裹

  • 特殊的属性,使用className来代替classhtmlFor代替 label 的for属性

  • 标签上写 style 的话,要用 js 对象

  • css 模块化

    npm i style-loader css-loader -D

    配置 webpack.config.js 的 rules

     // webpack.config.js
     {
          test: /\.scss$/,
          use: ['style-loader', {
              loader: 'css-loader',
              options: {
                  modules: {
                      localIdentName: '[path][name]-[local]-[hash:5]'
                  },
                  sourceMap: true
              }
          }, 'sass-loader']
      },
    

    组件中import cssobj from './demoStyle.css'

    给 css 模块化use['css-loader?modules'],但是模块化只针对.class 和#id 选择器,不会将标签选择器模块化

    给 css 自定义生成类名格式use['css-loader?localIdentName=[path][name][local][hash:6]']

    其可选参数是:

    • [path]表示样式表相对于项目根目录所在路径
    • [name]表示样式表文件名称
    • [local]表示样式的类名定义名称
    • [hash:length]表示最多 32 位的 hash 值

    css 可选:local()(开启 modules 之后默认 不用写)或者全局:global(.xxclass)(不会被模块化)

    对于第三方的样式表,规定都以.css 结尾,这样的话 我们不会为普通的.css 启用模块化 直接 import ‘bootstrap’就好,自己的样式表都以.scss 或.less 结尾,于是 只为.scss.less文件启用模块化

    安装npm i sass-loader node-sass -D

  • 绑定事件
    事件绑定机制,相对于原生,是小驼峰命名,事件值必须是函数
    button onClick={function(){}}>按钮</button>
    <button className="Btn" onClick={() => this.myClickFn()}>点击</button>

组件 - 创建组件的两种方式

  • 父组件向子组件传递数据
  • 使用{…obj}属性扩散传递数据
  • 注意:组件的名称必须是大写
  • 将组件封装到单独的文件中
  • 引入时省略.jsx后缀名
  1. 函数式组件 - 使用构造函数创建组件
    没有 state 状态,可以使用 Hooks
    如果要接受外间传递的数据,需要在构造函数的参数列表中使用 props 来接收
    必须向外 return 一个合法的 jsx 创建的虚拟 dom(React 元素)

    import React, { useState, useEffect } from "react";
    export default function FunctionComponent(props) {
      const [date, setDate] = useState(new Date());
      useEffect(() => {
        //相当于componentDidMount、componentWillUnmount、componentDidUpdate的集合
        const timer = setInterval(() => {
          setDate(new Date());
        }, 1000);
        return () => clearInterval(timer);
      }, []);
      return (
        <div>
          <h3>FunctionComponent</h3>
          <p>{date.toLocaleTimeString()}</p>
        </div>
      );
    }
    
  2. CLASS 组件 - 使用 class 关键自关键组件
    有 state 状态

    import React, { Component } from "react";
    export default class ClassComponent extends Component {
      constructor(props) {
        super(props);
        //存储状态
        this.state = {
          date: new Date(),
        };
      }
      //组件挂载完成之后执行
      componentDidMount() {
        this.timer = setInterval(() => {
          //更新state,不能用this.state
          this.setState({
            date: new Date(),
          });
        }, 1000);
      }
      //组件卸载之前执行
      componentWillUnmount() {
        clearInterval(this.timer);
      }
      render() {
        const { date } = this.state;
        return (
          <div>
            <h3>ClassConponent</h3>
            <p>{date.toLocaleTimeString()}</p>
          </div>
        );
      }
    }
    
  3. 两者的区别

    1. class 关键子创建的组件有自己的私有数据和生命周期,但是 function 创建的组件 只有 props,没有自己的私有数据和生命周期
    2. 用构造函数创建的组件:叫做“无状态组件”,无 state 和生命周期,但是运行效率较高,很少用
    3. 用 class 关键字创建出来的组件:叫做“有状态组件”

Class 组件

setState

因为 React 是单向绑定 单向数据流(js=>界面例:对于 input,需要三步,1 手动监听 onChange 事件,2 在事件中拿到最新的 e.target.value,3 调用 this.setState({})),如果要为 state 中的数据重新赋值,要用 react 提供的setState(updater, [callback])
将 setState() 视为请求而不是立即更新组件的命令。React 会延迟调用它,然后通过一次传递更新多个组件。React 并不会保证 state 的变更会立即生效。因为他会是将对组件 state 的更改排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。在合成时间和生命周期中是异步的批量更新,在 setTimeout 和原生事件中和回调中是同步的
通常第一个参数 updater 接受对象类型,如需基于之前的 state 来设置当前的 state,请将updater作为函数(state, props) => stateChange,updater 函数中接收的 state 和 props 都保证为最新。updater 的返回值会与 state 进行浅合并。

react 的生命周期

旧的生命周期:

image.png

  1. 组件创建阶段,只执行一次
    • componentWillMount
    • render
    • componentDidMount
  2. 组件运行阶段,根据 props 属性或 state 状态数据的改变,有选择的执行 0 到多次
    • componentWillReceiveProps(nextProps)
      1. 该方法只在 props 引起的组件更新过程中,才会被调用。state 引起的组件更新并不会触发。
      2. nextProps 是父组件传递给当前组件的新的 props
      3. nextProps 的值可能与子组件当前 props 的值相同,因此往往需要比较他俩的值来决定是否执行 props 发生变化后的逻辑。
      4. 在该方法中调用 setState,只有 render 以及之后的方法中。this.state 指向的才是更新后的 state。在之前的 shouldComponentUpdate、componentWillUpdate 中。this.state 指向的还是更新前的 state
    • shouldComponentUpdate
    • componentWillUpdate
    • render
    • componentDidupdate
      shouldComponentUpdate 与 componentWillUpdate 中不能调用 this.setState,否则会引起循环调用问题,render 永远无法被调用,组件也永远无法渲染
  3. 组件销毁阶段,只执行一次
    • componentWillUnmount

新的 v16.4 之后生命周期

图示来源

image.png

生命周期详解 参考教程
https://www.izhaoo.com/2020/05/17/react-lifecycle/

  1. 挂载阶段

    • constructor

    • static getDerivedStateFromProps(props, state)[新增]

      1. 一个静态方法 基于 props 的派生 state,会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。不能在此函数里面使用 this,这个函数有两个参数 props 和 state,分别指接收到的新参数和当前的 state 对象
      2. 此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props。
      3. 该函数会在挂载时,接收到新的 props,调用了setStateforceUpdate时被调用,这个方法就是为了取代之前的componentWillMountcomponentWillReceivePropscomponentWillUpdate
      4. getDeriveStateFromProps 被设计成一个 static 方法,就是纯粹的获取父组件 props,增量更新本组件的 state
    • componentWillMount/UNSAFE_componentWillMount[废弃]

    • render

    • componentDidMount

      1. 依赖 DOM 节点的操作可以放到这个方法中。这个方法通常还用于向服务端请求数据
  2. 更新阶段
    组件被挂载到 DOM 后,组件的 props 或 state 改变会引起组件的更新。
    props 引起的更新,本质上是由渲染该组件的父组件引起的(也就是当父组件的 render 方法被调用时,组件会发生更新过程),无论 props 是否改变,父组件 render 方法每一次调用,都会导致组件更新。
    state 引起的组件更新,是通过 this.setState 修改组件的 state 触发的。

    • componentWillReceiveProps/UNSAFE_componentWillReceiveProps[废弃]

    • static getDerivedStateFromProps(props, state)[新增] :同上

    • shouldComponentUpdate(nextProps,nextState)

      1. 该方法决定组件是否继续执行更新过程。当该方法返回 true(默认值)时继续执行,返回 false 时停止执行
      2. 一般通过比较 nextProps、nextState 与组件当前的 props、state 来决定返回值。
      3. 该方法可用来减少不必要的渲染,从而优化组件的性能
    • componentWillUpdate/UNSAFE_componentWillUpdate[废弃]

    • render

    • getSnapshotBeforeUpdate(prevProps, prevState)[新增]

      1. 这个方法在render之后,componentDidUpdate之前调用,表示之前的属性和之前的 state
      2. 这个函数有一个返回值,会作为第三个参数传给componentDidUpdate,如果你不想要返回值,可返回 null,不写的话控制台会 warning 此方法必须配合componentDidUpdate方法一起使用
      3. 此方法 为了取代之前的componentWillUpdate
    • componentDidUpdate(prevProps, prevState, snapshot)

      1. 当组件更新后,可以在此处对 DOM 进行操作。如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。(例如,当 props 未发生变化时,则不会执行网络请求)
  3. 卸载阶段

    • componentWillUnmount

组件复合

  • 组件通过props属性,传递给子组件数据。
  • 相当于 VUE 的slot,React 中用props.children表示组件内子内容。

PureComponent

纯组件
即 定制了 shouldComponentUpdate(nextProps,nextState){return nextState.xx!==this.state.xx || nextProps.xx!==this.props.xx}后的 Component。比如如果赋予 React 组件相同的 props 和 state,render() 函数会渲染相同的内容,那么在某些情况下使用 React.PureComponent 可提高性能

  • 必须要用class 组件形式,⽽且要注意是浅比较,只比较一层,当 xx 是对象 重新赋值时会永远判断为相等导致子组件不会更新。可使用immutable对象加速嵌套数据的比较 或通过解构赋值/Object.assign 给 state 第一层属性重新赋值新 obj
  • 其中定制的shouldComponentUpdate(nextProps,nextState)将跳过所有子组件树的 prop 更新。因此,请确保所有子组件也都是纯组件,否则即使有自定义 shouldComponentUpdate 也会被 warning&忽视
export default class PureComponentPage1 extends PureComponent {}

函数组件

import React, { useState } from "react";
export default function HookPage(props) {
  // 声明⼀一个叫 “count” 的 state 变量量,初始化为0
  const [count, setCount] = useState(0);
  return (
    <div>
      <h3>HookPage</h3>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>add</button>
    </div>
  );
}

Hook 使用规则

  • 只能在 函数最外层 调用 Hook。不要在循环、条件判断或者子函数中调⽤。
  • 只能在 函数组件/自定义 Hook 中调用 Hook。不要在其他普通 JavaScript 函数中调用。

几个 Hook 方法

Hook 方法 & Class 组件生命周期关联 > 几个 Hook 方法详解 > useImperativeHandle & forwardRef 用法详解

  • useState
    const [state, setState] = useState(initialState)
    返回一个 state,以及更新 state 的函数。setState(newstate | prevCount => prevCount - 1) 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列,如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值

  • useEffect

    useEffect(() => {
      // 在 componentDidMount,以及 []内变量 更改时 componentDidUpdate 执行的内容
      const subscription = props.source.subscribe();
      return () => {
        // 相当于 componentWillUnmount 执行的内容
        subscription.unsubscribe();
      };
    }, [props.source]);
    

    赋值给 useEffect 的函数会在组件渲染到屏幕之后执行与 componentDidMount 和 componentDidUpdate 相似

  • useContext
    useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 MyContext.Consumer

    const themes = {
      light: {
        foreground: "#000000",
        background: "#eeeeee",
      },
      dark: {
        foreground: "#ffffff",
        background: "#222222",
      },
    };
    const ThemeContext = React.createContext(themes.light);
    function App() {
      return (
        <ThemeContext.Provider value={themes.dark}>
          <Toolbar />
        </ThemeContext.Provider>
      );
    }
    function Toolbar(props) {
      return (
        <div>
          <ThemedButton />
        </div>
      );
    }
    function ThemedButton() {
      const theme = useContext(ThemeContext);
      return (
        <button style={{ background: theme.background, color: theme.foreground }}>
          I am styled by theme context!
        </button>
      );
    }
    
  • useReducer
    const [state, dispatch] = useReducer(reducer, initialArg, init)
    useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等

    const initialState = { count: 0 };
    function reducer(state, action) {
      switch (action.type) {
        case "increment":
          return { count: state.count + 1 };
        case "decrement":
          return { count: state.count - 1 };
        default:
          throw new Error();
      }
    }
    
    function Counter() {
      const [state, dispatch] = useReducer(reducer, initialState);
      return (
        <>
          Count: {state.count}
          <button onClick={() => dispatch({ type: "decrement" })}>-</button>
          <button onClick={() => dispatch({ type: "increment" })}>+</button>
        </>
      );
    }
    

    惰性创建初始 state,将 init 函数作为 useReducer 的第三个参数,初始 state 将被设置为init(initialArg),这么做可以将用于计算 state 的逻辑提取到 reducer 外部,这也为将来对重置 state 的 action 做处理提供了便利

    function init(initialCount) {
      return { count: initialCount };
    }
    function reducer(state, action) {
      switch (action.type) {
        case "increment":
          return { count: state.count + 1 };
        case "decrement":
          return { count: state.count - 1 };
        case "reset":
          return init(action.payload);
        default:
          throw new Error();
      }
    }
    function Counter({ initialCount }) {
      const [state, dispatch] = useReducer(reducer, initialCount, init);
      return (
        <>
          Count: {state.count}
          <button
            onClick={() => dispatch({ type: "reset", payload: initialCount })}
          >
            Reset
          </button>
          <button onClick={() => dispatch({ type: "decrement" })}>-</button>
          <button onClick={() => dispatch({ type: "increment" })}>+</button>
        </>
      );
    }
    
  • useMemo
    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
    缓存计算数据的值
    如下:在 a 和 b 的变量值不变的情况下,memoizedValue 的值不变。即:useMemo 函数的第一个入参函数不会被执行,从而达到节省计算量的目的

    useMemo & useCallback 对比

  • useCallback

    const memoizedCallback = useCallback(() => {
      doSomething(a, b);
    }, [a, b]);
    

    缓存函数的引用
    如下:在 a 和 b 的变量值不变的情况下,memoizedCallback 的引用不变。即:useCallback 的第一个入参函数会被缓存,从而达到渲染性能优化的目的
    useCallback(fn, deps) 相当于 useMemo(() => fn, deps) 。 注意依赖项数组不不会作为参数传给“创建”函数。虽然从概念上来说它表现为:所有“创建”函数中引⽤用的 值都应该出现在依赖项数组中。未来编译器器会更更加智能,届时⾃自动创建数组将成为可能

  • useRef
    const refContainer = useRef(initialValue)
    useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

    function TextInputWithFocusButton() {
      const inputEl = useRef(null);
      const onButtonClick = () => {
        // `current` 指向已挂载到 DOM 上的文本输入元素
        inputEl.current.focus();
      };
      return (
        <>
          <input ref={inputEl} type="text" />
          <button onClick={onButtonClick}>Focus the input</button>
        </>
      );
    }
    

    请记住,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。

  • useImperativeHandle
    useImperativeHandle(ref, createHandle, [deps])

    when to use this Hook
    useImperativeHandle 应当与 forwardRef 一起使用:
    forwardRef 理解成把 useRef 实例的*指向ref={xxRef}*向下传递给某个元素实例,目的是让父组件可以操控子组件内元素
    而用了 useImperativeHandle 之后,useImperativeHandle 将拿到 forwardRef 传递过来的 ref,对该 ref.current 的具体实例值进行操控,

    import React, { useRef, useImperativeHandle, forwardRef } from "react";
    function Child(props, parentRef) {
      // 子组件内部自己创建 ref
      let focusRef = useRef();
      let inputRef = useRef();
      // 这个函数会返回一个对象
      // 该对象会作为父组件 current 属性的值
      // 通过这种方式,父组件可以操作子组件中的多个 ref
      useImperativeHandle(parentRef, () => ({
        focusRef,
        inputRef,
        name: "计数器",
        focus() {
          focusRef.current.focus();
        },
        changeText(text) {
          inputRef.current.value = text;
        },
      }));
      return (
        <>
          <input ref={focusRef} />
          <input ref={inputRef} />
          {/* <input ref={parentRef} type="text"/> */}
        </>
      );
    }
    const FancyInputEle = forwardRef(Child);
    export default function Home(props) {
      const parentRef = useRef("s");
      return (
        <div>
          <button
            onClick={() => {
              parentRef.current.focus();
              console.log(parentRef.current.focusRef.current.value);
              // parentRef.current.addNumber(666);// 因为子组件中没有定义这个属性,实现了保护,所以这里的代码无效
              parentRef.current.changeText("sd");
              console.log(parentRef.current.name);
            }}
          >
            focus
          </button>
          <button onClick={() => console.log(parentRef)}>value</button>
          <FancyInputEle ref={parentRef}></FancyInputEle>
        </div>
      );
    }
    
    1. 写在forwardRef内部,限定子组件的哪些实例值可以被父组件暴漏获取/哪些属性方法可以被父组件调用,避免使用 ref 这样的命令式代码&&重定义事件方法使用
    2. 父组件可以操作子组件中的多个 ref
    3. 在大多数情况下,应当避免使用 ref 这样的命令式代码。
  • useLayoutEffect
    其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,有点类似componentWillMount,useLayoutEffect 内部的更新计划将被同步刷新。
    image.png
    详情参考

    • useEffect 在全部渲染完毕后才会执行
    • useLayoutEffect 会在 浏览器 layout 之后,painting 之前执行
    • 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect
    • 可以使用它来读取 DOM 布局并同步触发重渲染
    • 在浏览器执行绘制之前 useLayoutEffect 内部的更新计划将被同步刷新
    • 尽可能使用标准的 useEffect 以避免阻塞视图更新
  • useDebugValue
    useDebugValue(value, (value) => value.Fn())
    自定义 hook内部设置,可用于在 React 开发者工具中显示自定义 hook 的标签

    function useFriendStatus(friendID) {
      const [isOnline, setIsOnline] = useState(null);
      // ...
      // 在开发者工具中的这个 Hook 旁边显示标签
      // e.g. "FriendStatus: Online"
      useDebugValue(isOnline ? "Online" : "Offline");
      return isOnline;
    }
    

    接受一个格式化函数作为可选的第二个参数,该函数只有在 Hook 被检查时才会被调用。它接受 debug 值作为参数,并且会返回一个格式化的显示值。
    useDebugValue(date, date => date.toDateString())

自定义 Hook

自定义 Hook 是⼀个函数,其名称必须以 “use” 开头,函数内部可以调⽤其他的 Hook。
其实就是个封装的函数

有时候我们会想要在组件之间重用一些状态逻辑。⽬前为止,有两种主流方案来解决这个问题:⾼阶组件和 render props。⾃定义 Hook 可以让你在不增加组件的情况下达到同样的目的。

function useClock() {
  const [date, setDate] = useState(new Date());
  useEffect(() => {
    console.log("date effect"); //只需要在didMount时候执行就可以了
    const timer = setInterval(() => {
      setDate(new Date());
    }, 1000); //清除定时器,类似willUnmount
    return () => clearInterval(timer);
  }, []);
  return date;
}

React-Native 的自定义 hook

import React, { useState, useEffect } from "react";
import { Linking, StyleSheet, Text, View } from "react-native";

const useMount = (func) => useEffect(() => func(), []);

const useInitialURL = () => {
  const [url, setUrl] = useState(null);
  const [processing, setProcessing] = useState(true);

  useMount(() => {
    const getUrlAsync = async () => {
      // Get the deep link used to open the app
      const initialUrl = await Linking.getInitialURL();

      // The setTimeout is just for testing purpose
      setTimeout(() => {
        setUrl(initialUrl);
        setProcessing(false);
      }, 1000);
    };

    getUrlAsync();
  });

  return { url, processing };
};

const App = () => {
  const { url: initialUrl, processing } = useInitialURL();

  return (
    <View style={styles.container}>
      <Text>
        {processing
          ? `Processing the initial url from a deep link`
          : `The deep link is: ${initialUrl || "None"}`}
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center", alignItems: "center" },
});

export default App;

React-router

react-router 包含 3 个库,react-router、react-router-dom 和 react-router-native。根据应⽤运⾏的环境选择安装 react-router-dom(在浏览器器中使用)或 react-router-native(在 rn 中使用)。react-router-dom 和 react-router-native 都依赖 react-router,所以在安装时,react-router 也会自动安装,创建 web 应⽤
npm install --save react-router-dom

基本组件

react-router 中奉行⼀切皆组件的思想,路由器-Router、链接-Link、路由-Route、独占-Switch、重定向-Redirect 都以组件形式存在

import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link,Switch } from "react-router-dom";
export default class RouterPage extends Component {
  render() {
    return (
      <div>
         <h3>RouterPage</h3>
         <Router>
            <Link to="/">⾸首⻚页</Link>
            <Link to="/user">⽤用户中⼼心</Link>
            {/* 根路路由要添加exact,实现精确匹配 */}
            <Switch>
               <Route
                  exact
                  path="/"
                  component={HomePage}
                  //children={() => <div>children</div>}
                  //render={() => <div>render</div>}
               />
               <Route path="/user" component={UserPage} />
               <Route component={Page404}></Route>
            </Switch>
         </Router>
      </div>
); }
}
class HomePage extends Component {
  render() {
    return (
      <div>
        <h3>HomePage</h3>
      </div>
); }
}
class UserPage extends Component {
  render() {
    return (
      <div>
        <h3>UserPage</h3>
      </div>
 );
class Page404 extends Component {
  render() {
    return (
      <div>
        <h3>404</h3>
      </div>
 );

Route 渲染内容的三种方式

Route 渲染方式互斥,优先级:children>component>render

  1. component={componentName}
    只在当 document.location 匹配的时候渲染
  2. render={()=>{}}
    只在当 document.location 匹配的时候渲染
  3. children:{()=>{}}
    不管 document.location 是否匹配都会被渲染。工作方法与 render 完全一样

React-redux

更新重点

  • context

    • xxContext.Provider [& xxContext.Consumer] Provider 的 value 值要提升到父节点的 state 变量
    • 子孙组件
      • class 组件: static contextType = xxContext ; const {obj} = this.context
      • fun 组件:const {obj} = useContext(xxContext)
      • 两种组件都可以用:<xxConsumer>{ctx=><div>内部接收格式必须是函数</div>}</xxConsumer>
  • HOC 高阶组件

    • 参数是组件,返回一个新的组件,本质上是个函数
    • 装饰器 @xx 只能用于 class 组件
    • 不要在 render 中(的 return 前)使用 HOC
  • 实现 fork 版 rc-field-form

    • Form - FieldContext, FieldContext.Provider; forwardRef & useImperativeHandle;
    • Field
    • useForm
  • react-dom - Portal 传送门

  • reducer

    • import {createStore} from 'redux' & createStore(reducerFun)
    • store.getState()
    • store.dispatch({type: ‘add’,payload: 100})
    • sotre.subscribe(()=>{this.forceUpdate()})
  • redux 中间件
    redux 只是个纯粹的状态管理器,dispatch()参数只接收对象,默认只支持同步。为了实现异步需要中间件的支持

    • createStore(reducerFn, applyMiddleware(中间件1,...中间件n))

    tips: 函数组件中怎么实现 forceUpdate:const [ignored, forceUpdate] = useReducer(x => x + 1, 0)
    在 function 组件中(class 组件 setState 不会比对前后 state),因为如果前后两次的值相同,useStateuseReducer都会放弃更新。而通常,不应该在 react 中修改本地 state,作为一条出路,可以用一个增长的计数器来在 state 没变的时候依然强制一次重新渲染
    为什么函数组件需要缓存 useCallback,useMemo。
    函数式编程的优点:自定义 hook 逻辑复用。与 UI 层脱离

Tips

React.Fragment

https://reactjs.org/docs/fragments.html

react 的父子通信

https://segmentfault.com/a/1190000021200567
https://www.shuzhiduo.com/A/KE5Q3Pr0zL/
react 发布和订阅
on subcribe
及 与 vue 区别

使用 JS 及 React Hook 时需要注意过时闭包的坑(文中有解决方法)

https://segmentfault.com/a/1190000020805789

精读《useEffect 完全指南》

  • useEffect 中,实现 willUnmount 生命周期,当有一种情况里面包含 state 时,需要拿到最新 state 值才执行的话:
    可以使用 useRef .current,这个值会绕过 useEffect 的 Capture Value 特性

  • 比较 React hooks 与 React class component 的性能:
    理论上其实就是比较 闭包与类 class 的性能,其实在现在浏览器两个差别不大。
    如果真要吹毛求疵,hooks 可能在某些方面还有自己的优点:hooks 不需要像 class 组件一样,创建类实例、绑定事件处理器等。
    hooks 会让你的代码结构更加简单,比如没有 hooks 之前,我们可可能更多的使用 render props、HOC、context 模式、容器组件模式等,容易导致你组件结构复杂或者嵌套,这势必对渲染有影响。
    很多人认为 hooks 性能差, 是因为每次渲染或者更新都需要构建一个新的函数上下文来执行。
    事实上也是这个样子,不过 React 将性能优化的方法交给了开发者来决定。比如 useCallbackuseMemo

hook 相比于 class 解决的问题

  • 函数组件没有自己的 state,即无状态,都是通过 props 获取父组件的状态,hooks 提供了 useState;不能监听生命周期 -> 有了 useEffect 聚合多个生命周期
  • hooks 写法上简洁,相关业务逻辑更聚合,使用 class 组件经常会出现一个功能出现在两个生命周期函数内的情况
  • 计算的越少,缓存的越多,性能越好。
    1. 纯函数 确定输入一定会确定输出,有很多优化手段,比如:虽然 fc 相比 class 每次渲染或者更新都需要构建一个新的函数上下文来执行,但是多用 useMemo,useCallback 可优化;
    2. 无副作用所以线程安全,可以使用多核(webWorker)并行计算。
  • class 组件逻辑难以复用 需要使用 HOC,render props
    想要在 class component 外部访问到 setState(ReactDispatcher),
    class 需要利用回调函数,或者是发布订阅,比如加个 Redux,通过调用 store dispatch 间接调用到 setState。
    而 hooks 可以使用自定义 hooks 代替 render props,如例子
    所以,
    class 组件的画风就是@connect、mapXXXToProps、subscribe、dispatch 还有各种生命周期钩子;
    函数组件清一色的 useState,useReducer,useEffect,useXXX,setState

规范 & Reference

使用 JSX 隐式地依赖当前环境下有 React 这一对象,但在源码上并没有显式使用,这种情况下添加 import React from ‘react’;会造成一个没有使用的变量存在。

使用 babel-plugin-react-require 插件可以很好地解决这一问题,因此无需显式地编写 import React from ‘react’;这一语句。

踩坑记录

  1. 为什么要放弃使用 useCallback(useCallback 的缺点)

   转载规则


《React》 Ryan Who 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Vue Vue
Vue概述什么是 Vue.js Vue.js 是目前最火的一个前端框架,React 是最流行的一个前端框架(React 除了开发网站,还可以开发手机 App, Vue 语法也是可以用于进行手机 App 开发的,需要借助于 Weex) Vu
2019-09-19
下一篇 
Eslint & Tslint Eslint & Tslint
代码检查 eslint & tslint目前 TypeScript 的代码检查主要有两个方案:使用 TSLint或使用 ESLint+ typescript-eslint-parser 代码规范如果你写自己的项目怎么折腾都没关系,但
2019-09-18
  目录