React知识点整理

React 深入

html直接写React

<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
<div id="app"></div>

<script type="text/babel">
  class Foo extends React.Component {
        render() {
            return (
                <div>
                    <MouseTracker />
                </div>
            );
        }
    }
    ReactDOM.render(<Foo/>, document.getElementById('app'))
</script>

react渲染

从拿到最新的数据,到将数据在页面中渲染出来,可以分为两个阶段。

  • 调度阶段。这个阶段React用新数据生成新的Virtual DOM,遍历Virtual DOM,然后通过Diff算法,快速找出需要更新的元素,放到更新队列中去。
  • 渲染阶段。这个阶段 React 根据所在的渲染环境,遍历更新队列,将对应元素更新。在浏览器中,就是跟新对应的DOM元素。除浏览器外,渲染环境还可以是 Native,硬件,VR 等

react - fiber

Fiber是什么

  • 以前react更新是stack reconciler (调节器)是递归更新子组件,在更新一颗很大的dom树,任何交互和渲染都会被阻塞。
  • 新的调度策略–Fiber reconciler ,React fiber就是把大的任务,分解为小任务,然后执行完小任务之后,会去看看有没有新任务需要执行。
  • Fiber 是一种轻量的执行线程,同线程一样共享定址空间,线程靠系统调度,并且是抢占式多任务处理,Fiber 则是自调用,协作式多任务处理

Fiber原理

  • requestIdleCallback 是浏览器提供的一个 api,可以让浏览器在空闲的时候执行回调,在回调参数中可以获取到当前帧剩余的时间,fiber 利用了这个参数,判断当前剩下的时间是否足够继续执行任务,如果足够则继续执行,否则暂停任务,并调用 requestIdleCallback 通知浏览器空闲的时候继续执行当前的任务
  • Fiber为不同的任务设置不同的优先级,同步任务,优先级最高,fiber 架构中一种数据结构就叫做fiber,fiber是一个对象,stateNode就是节点实例的对象, fiber 基于链表结构,拥有一个个指针,指向它的父节点子节点和兄弟节点,在 diff 的过程中,依照节点连接的关系进行遍历

Fiber更新阶段

  • Reconcile阶段。此阶段中,依序遍历组件,通过diff 算法,判断组件是否需要更新,给需要更新的组件加上tag。遍历完之后,将所有带有tag的组件加到一个数组中。这个阶段的任务可以被打断。
  • Commit阶段。根据在Reconcile阶段生成的数组,遍历更新DOM,这个阶段需要一次性执行完。如果是在其他的渲染环境–Native,硬件,就会更新对应的元素。

Fiber的问题

  • 由于 reconciliation 的阶段会被打断,可能会导致 commit 前的这些生命周期函数多次执行
  • 还有一个问题是饥饿问题,意思是如果高优先级的任务一直插入,导致低优先级的任务无法得到机会执行,这被称为饥饿问题

setState流程

  1. setState 只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的。
  2. setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 .setState(partialState, callback) 中的callback拿到更新后的结果。
  3. setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。

具体的代码流程如下

  • this.updater.enqueueState(this, partialState, callback, 'setState)
  • 通过this,获取到fiber
  • 计算过期时间
  • 创建一个带有优先级的update update.payload = partialState
  • enqueueUpdate(fiber, update)
  • 如果没有更新队列,就创建一个,是一个链表
  • appendUpdateToQueue
  • scheduleWork === scheduleUpdateOnFiber(fiber,过期时间)
  • 如果setState是在合成时间里,要执行event之类的一些操作,如 batchedEventUpdates
  • 如果setState是在生命周期中,要执行生命周期之类的一些操作
  • flushSyncCallbackQueue 完成更新 performSyncWorkOnRoot
  • commitRoot 完成更新

高阶组件

类型1,函数式高阶组件,参数是组件,本质上还是函数的封装

// 抽取公用逻辑,操作props
function Foo(props) {
    return (
        <div id='foo'>
            <input type="text" {...props}/>
        </div>
    )
}


function ppHOC(WrappedComponent) {
    return class PP extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                name: ''
            }
            this.onNameChange = this.onNameChange.bind(this)
        }
        onNameChange(event) {
            this.setState({
                name: event.target.value
            })
        }
        render() {
            const newProps = {
                name: {
                    value: this.state.name,
                    onChange: this.onNameChange
                }
            }
            return <WrappedComponent {...this.props} {...newProps}/>
        }
    }
}

// 或者可以使用装饰器方式 
const HocFoo = ppHOC(Foo);

类型2,基于继承的高阶组件 ,反向继承高阶组件,可以实现
渲染劫持(Render Highjacking)操作 state


class Foo extends React.Component {

    constructor(props) {
        super(props)
        this.state = {
            value: '1'
        }
    }

    componentDidMount() {
        console.log('componentDidMount Foo')
    }

    render() {
        return (
            <div id='foo'>
                {
                    this.state.value
                }
            </div>
        )
    }
}

class Hoc extends Foo {

    constructor(props){
        super(props);
        this.state = {
            ...this.state,
            name: 'foo'
        }
    }

    componentDidMount() {
        super.componentDidMount();
    }

    render() {
        console.log(this.state)
        return (
            <div id='hoc'>
                {super.render()}
                {super.render()}
            </div>
        )
    }
}

render props

就是渲染props,抽离组件,或者直接变成子组件

    class Mouse extends React.Component {
        constructor(props) {
            super(props);
            this.state = {x: 0, y: 0};
        }

        handleMouseMove = event => {
            this.setState({
                x: event.clientX,
                y: event.clientY
            });
        }

        render() {
            return (
                <div style={{height: '100vh'}} onMouseMove={this.handleMouseMove}>
                    <h1>移动鼠标!</h1>
                    {this.props.render(this.state)}
                </div>
            );
        }
    }

    function Pos(props) {
        return (
            <p>当前的鼠标1 位置是 ({props.mouse.x}, {props.mouse.y})</p>
        )
    }


    class Foo extends React.Component {
        render() {
            return (
                <div>
                    <Mouse render={mouse => <Pos mouse={mouse}/>}/>
                </div>
            );
        }
    }

    // 或者可以通过children写成这样
    class Mouse extends React.Component {
        constructor(props) {
            super(props);
            this.state = {x: 0, y: 0};
        }

        handleMouseMove = event => {
            this.setState({
                x: event.clientX,
                y: event.clientY
            });
        }

        render() {
            return (
                <div style={{height: '100vh'}} onMouseMove={this.handleMouseMove}>
                    <h1>移动鼠标!</h1>
                    {this.props.children(this.state)}
                </div>
            );
        }
    }


    class Foo extends React.Component {
        render() {
            return (
                <div>
                    <Mouse>
                        {mouse =>  <p>当前的鼠标1 位置是 ({mouse.x}, {mouse.y})</p>}
                    </Mouse>
                </div>
            );
        }
    }
// 或者可以直接这样
    class Mouse extends React.Component {
        constructor(props) {
            super(props);
            this.state = {x: 0, y: 0};
        }

        handleMouseMove = event => {
            this.setState({
                x: event.clientX,
                y: event.clientY
            });
        }

        render() {
            return (
                <div style={{height: '100vh'}} onMouseMove={this.handleMouseMove}>
                    <h1>移动鼠标!</h1>
                    {React.cloneElement(this.props.children, this.state)}
                </div>
            );
        }
    }

    function Pos1({x, y}) {
        return (
            <p>当前的鼠标1 位置是 ({x}, {y})</p>
        )
    }


    class Foo extends React.Component {
        render() {
            return (
                <div>
                    <Mouse>
                        <Pos1 />
                    </Mouse>
                </div>
            );
        }
    }


react给子元素props加一些属性

   const newChildren = React.Children.map(this.props.children, children =>
      React.cloneElement(children, { setFocus: this.setFocus }),
    );

Ref

Ref 3种使用方式,函数组件,没有实例this,就不能用这种ref

class Foo extends React.Component {
  constructor(props) {
    super(props);
    this.ref3 = React.createRef();
  }


  componentDidMount() {
    // string ref 要被废弃,不建议使用
    console.log(this.refs.ref1)
    // function ref 
    console.log(this.ref2)
    // object ref 
    console.log(this.ref3)
  }
  
  render() {
    return (
      <div>
        <p ref='ref1'>ref1</p>
        <p ref={r => {this.ref2 = r;}}>ref2</p>
        <p ref={this.ref3}>ref3</p>
      </div>
    )
  }
}


useRef

useRef和createRef一样都可以拿到dom,但是useRef返回的永远都是一样的,
就是 useRef第一次拿不到dom,以后哪怕组件刷新也会返回一个相同的对象。
useRef会在每次渲染时返回同一个 ref 对象,在整个组件的生命周期内是唯一的。
createRef每次都创建新的ref。

const Code = props => {
  let ref = useRef(null);
  return (<div ref={ref}><div>)
}

// ref的变化,从null到真实dom不会再次渲染,要想拿到,需要使用 useCallback
const ref = useCallback(node => {
  if (node !== null) {
    console.log(node);
  }
}, []);

forwardRef

解决父组件无法获取子组件的ref的问题

const Demo1 = React.forwardRef((props, ref) => <input ref={ref}/>);
class Foo extends React.Component {
  constructor(props) {
    super(props);
    this.ref4 = React.createRef();
  }
  componentDidMount() {
    console.log(this.ref4.current.value = 'jaja')
  }
  render() {
    return (
      <div>
        <Demo1 ref={this.ref4}/>
      </div>
    )
  }
}

context


const {Provider, Consumer} = React.createContext('default');

class App extends React.Component {
  state = {
    name: 'default'
  }

  componentDidMount() {
    setTimeout(() => {
      this.setState({
        name: '100s'
      })
    }, 1000)
  }

  render() {
    return (
      <div id='app'>
        <Provider value={this.state.name}>
          <Foo/>
        </Provider>
      </div>
    )
  }
}


class Foo extends React.Component {
  render() {
    return (
      <Consumer>
        {v => <h2>{v}</h2>}
      </Consumer>
    )
  }
}

concurrent-mode

concurrent-mode包裹的组件,会有一个比较低的优先级

suspense lazy

suspense包裹的组件,只有到所有子组件都加载好,才不会再显示fallback

const Data1 = React.lazy(() => import('./data1'));
const Data2 = React.lazy(() => import('./data2'));

const Demo = () => {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Data1/>
      <Data2/>
    </Suspense>
  )
}


useState

useState可以使用对象,但是改变后的值必须是一个全新的对象

useEffect

  • useEffect 在每次渲染之后执行,
  • useEffect 如果多个,按照顺序执行,
  • useEffect 依赖不写,每次都会执行,
  • useEffect 依赖空数组,类似didMount
  • useEffect 可以返回一个函数,类似didUnMount,第一次不会执行,以后更新的时候,会先执行这个,再执行effect中的其他逻辑

function Counter() {
  const [count, setCount] = useState(0);
  console.log(1111);

  useEffect(() => {
    console.log('no dep');
    console.log(document.getElementById('btn'));
  });
  useEffect(() => {
    console.log('count dep');
  }, ['count']);

  useEffect(() => {
    console.log('no dep didmount');
  }, []);

  useEffect(() => {
    console.log('effect with cb');
    const timer = setTimeout(() => {
      console.log('this is a timer');
    }, 2000);
    return () => {
      clearTimeout(timer);
    };
  }, []);
  
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)} id="btn">
        Click me
      </button>
    </div>
  );
}

useLayoutEffect

和 useEffect一样,但是它在dom完成之后同步执行,用它来读取dom并同步重新渲染
useLayoutEffect里调度的更新会被同步的flush,在浏览器有一个改变要重新渲染之前。
和componentDidMount and componentDidUpdate是一样的。
见过测试,使用useEffect动画正常,它卡顿。

useContext

就是在子组件中使用context,而不需要使用consumer

const MyContext = React.createContext();

function Foo() {
  let [data, setData] = useState(0);
  return (
    <div id="foo">
      <button onClick={() => setData(++data)}>add </button>
      <MyContext.Provider value={data}>
        <Foo1 />
      </MyContext.Provider>
    </div>
  );
}

function Foo1() {
  let v = useContext(MyContext);
  return <h2 id="foo1">{v}</h2>;
}

React.memo

包裹一个函数组件,props浅比较,防止无用的更新,类似pureComponent的功能

const Foo2 = React.memo(props => {
  console.log('foo2');
  return (
    <div>
      <div>data from props = {props.c}</div>
    </div>
  );
});

function Foo() {
  console.log('foo1');
  let [data, setData] = useState(0);

  const click = () => {
    data += Math.round(Math.random());
    setData(data);
  };

  return (
    <div id="foo">
      <button onClick={click}>add data</button>
      <Foo2 c={data} />
    </div>
  );
}

useReducer

useReducer可以使用action把一个state变成另外一个state 。
以下几种情况使用useReducer更好,逻辑有多个子值,集中处理。

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 (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </div>
  );
}


children

// 1 map 
function Foo1(props) {
  return (
    <div>
      {
        // props.children.map(c => [c, c])
        React.Children.map(props.children, c => [c, c])
      }
    </div>
  )
}


class App extends React.Component {
  state = {
    name: 'default'
  }

  render() {
    return (
      <div id='app'>
        <Foo1>
          <span>1</span>
          <h2>a</h2>
        </Foo1>
      </div>
    )
  }
}

useCallBack

返回一个memozized回调函数

import React, { useState, useCallback, useEffect } from 'react';

function Child({ event }) {
  console.log('child-render');
  // 第五版
  useEffect(() => {
    console.log('child-useEffect');
    event();
  }, [event]);
  return (
    <div>
      <p>child</p>
      {/* <p>props-data: {data.data && data.data.openCode}</p> */}
      <button onClick={event}>调用父级event</button>
    </div>
  );
}
const url = 'http://localhost:3322/data';

export default function Demo8() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState(0);

  // demo1 每次render的handle都是新的函数,每次都可以拿到最新的data
  // const handle = async () => {
  //   const response = await fetch(url);
  //   const res = await response.json();
  //   console.log('handle', res);
  //   setData(res);
  //   // setData不是同步的,所以setData之后,拿到的data是上一次的
  //   // 但是在组件函数中,就可以拿到最新的
  //   console.log('fn', data);
  // };

  // demo2 和 demo1的效果一样的
  // const handle = useCallback(async () => {
  //   const response = await fetch(url);
  //   const res = await response.json();
  //   console.log('接口的新数据', res);
  //   setData(res);
  //   // 这里的data还是上一次的data
  //   console.log('data in cb', data);
  //   console.log('\n');
  // });

  // 接口的新数据 0.3010873119062878
  // parent-render====> 0.3010873119062878
  // child-render
  // data in cb 0

  // 接口的新数据 0.07467124797072677
  // parent-render====> 0.07467124797072677
  // child-render
  // data in cb 0.3010873119062878

  // demo3 useCallback 如果有第二个参数depth,函数会被记忆化,所以每次data都是第一次的值(闭包)
  // const handle = useCallback(async () => {
  //   const response = await fetch(url);
  //   const res = await response.json();
  //   console.log('api', res);
  //   setData(res);
  //   // 这里拿不到上一个数据了,data永远是0
  //   console.log('useCallback', data);
  // }, []);

  // demo4 handle依赖于count
  // 每当count改变,都会生产一个全新的handle,会触发子组件的effect
  const handle = useCallback(async () => {
    const response = await fetch(url);
    const res = await response.json();
    console.log('res = ', res);
    setData(res);
    console.log('parent-useCallback', data);
  }, [count]);

  console.log('parent-render', data);
  return (
    <div>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        count++
      </button>
      <p>count:{count}</p>
      <p>data: {data}</p>
      <p>-------------------------------</p>
      <Child event={handle} />
    </div>
  );
}

useMemo

  1. 返回一个 memoized 值,和useCallback一样,当依赖项发生变化,才会重新计算 memoized 的值。
  2. useMemo和useCallback不同之处是:它允许应用于任何值类型(不仅仅是函数)。
  3. useMemo在render之前执行。
  4. 主要区别是 React.useMemo 将调用 fn 函数并返回其结果,而 React.useCallback 将返回 fn 函数而不调用它
import React, { useState, useMemo } from 'react';

function Demo9() {
  let [count, setCount] = useState(0);

  // 这是一个方法
  // let handle = () => {
  //   console.log('handle', count);
  //   return count;
  // };

  // 依赖为空 Child组件拿到的handle永远为0
  // let handle = useMemo(() => {
  //   console.log('handle1', count);
  //   return count;
  // }, []);

  const handle = useMemo(() => {
    console.log('handle2', count);
    // 大计算量的方法
    return count;
  }, [count]);

  console.log('render-parent');

  function click() {
    setCount(count++);
  }

  return (
    <div>
      <p>
        demo9: {count}
        <button onClick={click}>++count</button>
      </p>
      <p>-------------------</p>
      <Child handle={handle} />
    </div>
  );
}

function Child({ handle }) {
  console.log('\n render-child');
  console.log('handle', handle);
  return (
    <div>
      <p>child</p>
      <p>handle{handle}</p>
    </div>
  );
}
export default Demo9;

useImperativeHandle

自定义ref的返回值,使用它可以把子组件的方法,暴露给ref,父组件就可以调用


const ChildCom = React.forwardRef(
	(props,ref) => {
      useImperativeHandle(ref, () => ({
        getData
      }))
      const getData = () => {
        // to do something
	    }
    
     return <div>ChildCom</div>
   }
);

react-dnd

  • React DnD 内置用了redux,
  • 基于h5的拖拽api,但是这个api在触摸屏不管用,这就是为什么用了一个可插拔的方式来实现的react-dnd,
    需要 html5-backend这个库,
  • backend handles the DOM events, 这种backend类似react的事件处理,抽象解决不同浏览器的差异,专注于处理原生dom,backends做的是把浏览器事转化为react-dnd可以处理的redux的action,
  • react-dnd像redux一样,处理的是数据,不是view,所以一个元素拖动的时候,叫做某种类型的item被拖动了,
  • item是用来描述被拖动的元素,例如在看板中的,一个卡片,描述为
    { cardId: 42 }, 把拖拽数据描述为简单对象可以解耦组件,互不影响彼此
  • type 是用来描述一类item的字符串 types 用来指定drag source和drop target是兼容的, 就像redux的action的type
  • Monitors drag和drop都是有状态的,monitor是dnd暴露状态(通过包裹内部存储的状态)给你的组件,通过monitor可以更新props来实现拖拽效果
  • 对于要更新monitors的组件,可以声明一个monitors作为参数的collecting方法,dnd会实时更新collecting方法来merge state到monitors来更新组件
  • The connectors let you assign one of the predefined roles (a drag source, a drag preview, or a drop target) to the DOM nodes in your render function,
    connectors 方法让你在render方法中指定链接到某一种角色(drag source等)到dom node
  • connectors是collect方法的第一个参数
function collect(connect, monitor) {
  return {
    highlighted: monitor.canDrop(),
    hovered: monitor.isOver(),
		// 怎么链接dropTarget 
    connectDropTarget: connect.dropTarget(),
  }
}

// 然后在render方法中,就可以获取monitors的属性了
render() {
  const { highlighted, hovered, connectDropTarget } = this.props;

  // connectDropTarget包裹的元素,是一个合法的drop target 并且它的hover事件等
	// 要交给backend来做 
  return connectDropTarget(
    <div className={classSet({
      'Cell': true,
      'Cell--highlighted': highlighted,
      'Cell--hovered': hovered
    })}>
      {this.props.children}
    </div>
  );
}
  • Drag Sources and Drop Targets 都是高阶组件

自定义hook1-useCount

function useCount(initValue, step) {
  const [count, setCount] = useState(initValue);

  const add = () => {
    let v = count + step;
    setCount(v);
  };

  const minus = () => {
    let v = count - step;
    setCount(v);
  };

  const reset = () => {
    setCount(initValue);
  };

  return [count, add, minus, reset];
}

export default function RootComponent() {
  const [count, add, minus, reset] = useCount(100, 4);
  return (
    <div id="app">
      <div>
        <button>{count}</button>
        <button onClick={add}>add</button>
        <button onClick={minus}>minus</button>
        <button onClick={reset}>reset</button>
      </div>
    </div>
  );
}



自定义hook2

const useInput = (init = '') => {
  const [value, setValue] = useState(init);
  const bind = { value, onChange: e => setValue(e.target.value) };
  const reset = () => setValue(init);
  return [value, bind, reset];
};

const Demo = () => {
  const [value1, bind1, reset1] = useInput('abc');
  return (
    <div>
      <div>value1: {value1}</div>
      <input type="text" {...bind1} />
      <button onClick={reset1}>reset1</button>
    </div>
  );
};

自定义hook3

const useSize = () => {
  const getSize = () => ({
    width: window.innerWidth,
    height: window.innerHeight
  });
  const [size, setSize] = useState(getSize());
  const handleResize = () => {
    setSize(getSize());
  };
  useEffect(() => {
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  return size;
};

const Demo2 = () => {
  const size = useSize();
  return (
    <div>
      size:{size.width},{size.height}
    </div>
  );
};

自定义hook-usePrevious


function App() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  return (
    <div>
      <h1>
        Now: {count}, before: {prevCount}
      </h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

function usePrevious(value) {
  const ref = useRef();

  // 只有在value变,才会重新执行
  useEffect(() => {
    ref.current = value;
    console.log(ref);
  }, [value]);

  // 返回上一个值,发生在上边的Effect执行前的更新
  return ref.current;
}

//和下边的效果一样的
let data = {
  a: null
};
function usePrevious(value) {
  // 只有在value变,才会重新执行
  useEffect(() => {
    data.a = value;
  }, [value]);

  // 返回上一个值,发生在上边的Effect执行前的更新
  return data.a;
}


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值