X

曜彤.手记

随记,关于互联网技术、产品与创业

吉 ICP 备10004938-2号

React 特性小结(v17.0.1)


记录一些之前理解了然后又忘了的知识点。

  1. 所有 React 组件都必须像纯函数一样确保它们的 props 不被更改
  2. 出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。在需要依赖值以更新下一个状态时,可以使用该方法的另一个形式:
this.setState((state, props) => ({
  // 此处为上一个 state;
  counter: state.counter + props.increment
}));
  1. JSX 回调函数中的 this 绑定问题:即 class 的方法默认不会绑定 this
class Comp extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log(`this is: ${this}`);
  }
  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}
class Comp extends React.Component {
  handleClick = () => {
    console.log(`this is: ${this}`);
  }
  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}
class Comp extends React.Component {
  handleClick() {
    console.log(`this is: ${this}`);
  }
  render() {
    return (
      <button onClick={() => this.handleClick()}>
        Click me
      </button>
    );
  }
}
  1. 向事件处理程序传递参数:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
  1. 让 render 方法返回 null,可以让 React 不进行任何渲染。
  2. 使用“索引”作为列表组件的 key 值,会导致性能变差,还可能引起组件状态问题。key 需要在兄弟节点之间保持唯一,生成多个不同的节点数组时,分散于不同数组中的节点可以使用重复的 key。
  3. 受控组件 & 非受控组件:前者输入的值始终由 React 的 state 驱动;后者表单控件的值由 DOM 元素自己来保存,而非组件的状态。
  4. 使用 props.children 可以将嵌套的子组件渲染到结果中
function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}
function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}
  1. 可以使用 React.lazySuspense 来实现组件的动态按需引入(基于路由的代码分割)。React.lazy 目前仅支持默认导出。
  2. 启发式 “diffing” 算法的两个假设:
  1. 生命周期方法:

  1. React 为何会添加 Hook 特性?
  1. Hook 使用规则:
  1. useEffect 中的“清理阶段”会在每次新的 effect 被执行前触发(对应 componentDidUpdate()),而非仅在组件 unmount 时触发。
  2. 自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。
  3. Hook APIs:

- useState

const [state, setState] = useState(initialState);
setState(prevState => prevState + 1);
// 合并更新对象;
setState(prevState => {
  // 也可以使用 Object.assign;
  return {...prevState, ...updatedValues};
});
const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

- useEffect

- useReducer

useState 的替代方案,适用于 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等情况(将 state 的变化逻辑内聚到了 reducer 中)。

const initialState = { count: 0 };

// reducer 可以被放到组件中去,并可以使用 props(可能会使一些优化失效);
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(props) {
  const [state, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    const id = setInterval(() => {
      dispatch({ type: 'increment' });
    }, 1000);
    return () => clearInterval(id);
  }, [dispatch]);

  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}

- useCallback

返回一个 memoized 的回调函数。useCallback(fn, deps) 相当于 useMemo(() => fn, deps)通常和 React.memo 一起配合使用

function List({ items, onItemClick }) {
  return <div>{items.map(item => <div onClick={ onItemClick }>{item}</div>)}</div>;
}
const MemoList = React.memo(List)

function App() {
  const [val, setVal] = useState(0);
  const [items, setItems] = useState(['Item 1', 'Item 2']);
  // 为了保证每次 App 组件重新渲染时,传递给子组件的函数是“相等的”;
  const onItemClick = useCallback(event => {
    console.log('You clicked ', event.currentTarget);
  }, []);
  return (
    <>
      <button onClick={() => { setVal(v => v + 1) }}>Click {val} times!</button>
    <MemoList
      items={items}
      onItemClick={onItemClick}
    />
    </>
  );
}

- useMemo

返回一个 memoized 值。把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

function App() {
  const [staticVal, setStaticVal] = useState(10)
  const [val, setVal] = useState(0)

  const dealWithStaticVal = (v) => {
    let result = 0;
    for (let i = 0; i < v; ++i) { result += i * i; }
    return result;
  }
  // memorized value;
  const memorizedStaticVal = useMemo(() => dealWithStaticVal(staticVal), [staticVal])
  const handleClick = () => {
    setVal(v => v + 1);
  } 
  return (
    <>
      <button onClick={handleClick}>Click Me!</button>
      <div>Static: {memorizedStaticVal}</div>
      <div>Dynamic: {val}</div>
    </>
  )
}

- useRef

useRef 是一种“选择退出”渲染一致性的方法,它会返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变(即每次返回的都是同一个值引用,多次执行只会创建一次。相对的,在函数组件中使用 createRef 时会,在每次渲染时创建包含初始值的新引用)。它可以很方便地保存任何可变值。需要注意:*变更 .current 属性不会引发组件重新渲染

function usePrevious(value) {
  const ref = useRef(0);
  useEffect(() => {
    ref.current = value;  // useEffect 会在 render 之后才会执行;
  });
  return ref.current;
}
function App() {
  const [val, setVal] = useState(0);
  const beforeVal = usePrevious(val);
  const handleClick = () => {
    setVal(v => v + 1);
  }

  return (
    <>
      <button onClick={handleClick}>Click Me!</button>
      <div>Latest: {val}</div>
      <div>Before: {beforeVal}</div>
    </>);
}

- useLayoutEffect

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

  1. 通常最好在 effect 内部去声明它所需要的函数。这样就能容易的看出那个 effect 依赖了组件作用域中的哪些值,以便将它们添加到依赖列表中。
  2. React.memo() 可以理解为函数组件下的 PureComponent。
  3. Effects 的“自给自足”(基于 useEffect 的回调函数模式):

- 依赖模式

useEffect(() => {
  const id = setInterval(() => {
    setCount(count + 1);  // 依赖于外部状态;
  }, 1000);
  return () => clearInterval(id);
}, [count]);

- 自给模式

useEffect(() => {
  const id = setInterval(() => {
    setCount(c => c + 1);  // 不再依赖外部状态,以“发送指令”的方式告知 React 具体的操作;
  }, 1000);
  return () => clearInterval(id);
}, []);
  1. 在组件内定义的函数每一次渲染都在变,因此将其作为 useEffect 的依赖会导致每次渲染都会触发 effect(本次渲染的函数与上一次的并不相同)。解决方案:
  1. 防止竞态问题,保证已过期的 render-screen 不会影响当前正在生效的渲染:利用布尔值进行跟踪
function Article({ id }) {
  const [article, setArticle] = useState(null);

  useEffect(() => {
    let didCancel = false;  // 跟踪当前渲染的有效性;
    async function fetchData() {
      const article = await API.fetchArticle(id);
      if (!didCancel) {   // 若当前渲染已取消则不渲染内容;
        setArticle(article);
      }
    }
    fetchData();
    return () => {
      didCancel = true;  // 新渲染完成时将旧渲染标记为“取消”;
    };
  }, [id]);
  // ...
}
  1. 如何用 useEffect 模拟 componentDidMount 生命周期?

和 componentDidMount 不一样,useEffect 会捕获 props 和 state。所以即便在回调函数里,拿到的还是初始的 props 和 state。如果想得到“最新”的值,可以使用 ref。不过,通常会有更简单的实现方式。effects 的心智模型和 componentDidMount 以及其他生命周期是不同的,试图找到它们之间完全一致的表达反而更容易使你混淆。想要更有效,你需要 “think in effects”,它的心智模型更接近于实现状态同步,而不是响应生命周期事件

  1. React 作为 UI 运行时的一些概念:

const [count, setCounter] = useState(0);
function increment() {
  // setCounter(c => c + 1);  // this works!
  setCounter(count + 1);
}
function handleClick() {
  // 由于“批量更新”,三个 setCount 并不会独立执行,因此获取到的 count 均为 0;
  increment();  // setCount(0 + 1).
  increment();  // setCount(0 + 1).
  increment();  // setCount(0 + 1).
}
  1. this.setState() 定义在 React 包中,它读取并使用由 React DOM 设置在每个已创建类上的 this.updater 字段,让 React DOM 安排并处理更新。类似的,Hooks 使用了一个 “dispatcher” 对象,代替了 updater 字段。当调用 React.useState()、React.useEffect() 或者其他内置的 Hook 时,这些调用被转发给了当前的 dispatcher。而各个渲染器会在渲染组件之前设置 dispatcher。
  2. React FiberYoutube


这是文章底线,下面是评论
  暂无评论,欢迎勾搭 :)