React 要点

336 阅读7分钟

React.createElement(type,props,children) 返回一个虚拟dom

  • JSX

    JSX=JS+XML

    JSX是一种语法糖,可以为js添加XML的语法扩展

    !!!: JSX的本质:将XML其转化为React.createElement,然后转化成虚拟DOM。在17之后直接编译成虚拟DOM

  • React 17 和 17 之前的差异:

  1. 在 React 17 新增了 JSX-runtime,可以将 JSX 直接编译成 虚拟DOM

  2. 在 React 17 之前,JSX 将会被编译成 React.createElement();

在 17 及之后,如果模块中,只用到了 React 的 JSX ,则不需要引入 React 依赖.

  • state

    在React中想要更新状态,必须调用组件的setState 方法

    • setState 会进行浅合并,所以必须给定所有需要替换的state
    • setState批量更新时是异步操作,单独更新时是同步操作
  • 父子组件通信

    • 父传子:在父组件中注册子组件处传入信息,然后在子组件中用props接收。

    • 子传父:子组件调用从父组件传过来的回调函数,然后引起父组件变化。

    • context

      • context需要利用createContext()来创建,类似vue中的eventBus。
      import {createContext} from "react";
      
      const context = createContext();
      const {Provider,Consumer} = context;
      export {Provider,Consumer}
      
      export default context;
      
      • 在父组件中用Provider来给其子孙组件传递信息。

        import { Component } from "react";
        import Contatiner from "./container";
        import context,{Provider} from "./context";
        console.log(context);
        class App extends Component {
          state = {
            showName: "friend",
            nub: 1
          }
        
          render() {
            const {showName,nub} = this.state;
            return <div>
                <Provider
                  value={{
                    showName,nub
                  }}
                >
                  <Contatiner />
                </Provider>
            </div>
          }
        }
        export default App;
        
      • 在其子孙组件中可以通过Consumer或者static contextType=context来接收

        import { Component } from "react";
        import {Consumer} from "./context";
        export default class Nub extends Component {
          render(){
            return <Consumer>
                {({nub})=>{
                  //console.log(context);
                  return <p>{nub}</p>
                }}
              </Consumer>
          }
        }
        
        import { Component } from "react";
        import context from "./context";
        class Name extends Component {
          static contextType = context;
          render(){
            const {showName} = this.context;
            return <>
                <p>{showName}</p>
            </>
          }
        }
        
        export default Name;
        
  • 受控组件和非受控组件

    • 受控组件:类似于 vue 中,v-model

      当我们需要在表单控件之外,获取表单的内部状态时,可以将表单的内部状态和组件的状态进行绑定,这种情况下,组件的状态改变表单的状态也会随之改变,表单的状态改变,组件的状态也会随之改变,我们称之为 受控组件

      在使用受控组件时,必须添加 onChange 回调,在回调中监听 表单的状态改变,然后更新组件状态

          <input 
            type="text" 
            value={val}
            onChange={({target})=>{
                this.setState({
                  val:target.value
                })
            }}
          />
    
    • 非受控组件:

      当我们想要设置,表单控件的初始值时,如果直接使用 value 或者 checked 的话,react 会认为我们想要实现的是 受控组件,这是就会提示我们添加 onchange 回调,否则 控件会变成只读的。这时可以使用 defaultValue 或 defaultChecked

          <input type="text" defaultValue="你爱写啥写啥" />
    

类组件

  • 生命周期 react16.4

    • 挂载阶段

      • constructor(props) 组件初始化
      • static getDerivedStateFromProps(props,state) 将父组件传过来的 props 中内容关联到子组件的 state 中 。 注意 this 问题
      • render 构建当前组件的虚拟DOM
      • componentDidMount 组件挂载完成 - 通常在该生命周期函数,进行副作用处理
    • 更新阶段(1.调用setState;2.父组件更新引起子组件更新;3.调用forceUpdate进行更新)

      • static getDerivedStatefromProps(props,state)
      • shouldComponentUpdate(nextProps,nextState) -- 判断是否更新 return true 组件更新 || false 不在继续执行更新流程
      • render()
      • getSnapshotBeforeUpdate -- 获取更新前的DOM快照,该函数执行时react已经完成了新老DOM对比,即将更新真实DOM,我们可以获取更新前的DOM,用于和更新后的DOM进行对比。该方法必须配合 componentDidUpdate 一块使用,该方法的返回值,会变成 componentDidUpdate 的 pervDOM 参数
      • componentDidUpdate(prevProps,prevState,prevDOM) -- 组件更新完成 处理副作用(请求)
    • 卸载阶段

      componentWillUnmount 组件即将卸载

  • pureComponent(可以只更新修改项,不更新未修改项)

    pureComponent会实现一个对props和state进行浅层比较,相当于Component使用了shouldComponentUpdate(nextProps,nextState).

函数组件

  • innerHTML

    let data = `<div>
      <h2>我想给大家一个大福利</h2>
      <p>大家想要吗?</p>
    </div>`;
    // function App(props) {
    //   return <div dangerouslySetInnerHTML={{
    //     __html:data  
    //   }}></div>
    // }
    function App(props) {
      return <div ref={node=>node.innerHTML=data}></div>
    }
    export default App;
    
    
  • ref

    • 当ref中是函数时,组件更新或挂载完成后执行该函数,并将该ref对象 传递(很少使用)
    • 当ref接收的是对象,那么将该对象存入ref中
  • children(类似于插槽)

    在父组件中注册子组件的位置,在子组件标签中间的内容,可以在子组件中通过props.children接收

    // 父组件
    import Child from "./child";  
    function App(props) {
      return <div>
        <Child info={"这是info属性"}>
            <p>这是通过 children 属性传递过来的信息</p>
            <p>这是通过 children 属性传递过来的第二条信息</p>
            {()=>{
              return <h3>随便来点啥</h3>
            }}
        </Child>
      </div>
    }
    export default App;
    
    
    // 子组件
    function Child(props) {
      console.log(props);
      return <div>
          <h2>子节点</h2>
          {props.children[0]}
          {props.children[1]}
          {props.children[2]()}
      </div>
    }
    export default Child;
    
  • hooks(用来解决函数组件中的逻辑复用问题)

    import { useState } from "react";
    function App() {
      const [nub,setNub] = useState(0);
      const [val,setVal] = useState("初始值");
      return <div>
          <p>{nub}</p>
          <button onClick={()=>{
            setNub(nub + 1);
          }}>递增</button>
          <p>{val}</p>
          <input 
            type="text" 
            value={val} 
            onChange={({target})=>{
              setVal(target.value);
            }}
          />
      </div>
    }
    export default App;
    
    • useState

      • const [state,setState]=useState("初始值"),其中setState不会进去浅合并操作。如果数据是state内的数据,需要进去合并操作。

        function App() {
          const [state,setState] = useState({
            nub: 1,
            count: 1
          });
          const {nub,count} = state;
          return <>
            <p>{nub}</p>
            <button onClick={()=>{
              setState({
                ...state,
                nub: nub + 1
              });
            }}>递增nub</button>
            <p>{count}</p>
            <button onClick={()=>{
              setState({
                ...state,
                count: count + 1
              });
            }}>递增count</button>
          </>;
        }
        
      • useState生成的数据是数组,所在在使用时要保证其调用顺序,不能使用ifforwhile等循环语句

      • 如果state是引用类型,那么setState传入的是原有引用,数据不更新,需要浅复制一个新引用

      const addMessage = (user, message) => {
        // 使用 useState 时,如果数据是引用类型,并且 setState 时,传入的还是原有引用,组件不更新
        let newData = data.data;
        newData.push({
          id: Date.now(),
          user,
          message,
          checked: false
        });
        setData({data:newData});
      };
    
    • useEffect() 副作用函数,用来取代生命周期函数

      useEffect(()=>{// 副作用函数
          *** 
          return ()=>{// 返回函数
              *** 
          } 
      },[依赖参数])
      
      • 挂载阶段:执行顺序从上到下。碰到useEffect就将其副作用函数传入一个链表,当组件挂载完成之后,执行副作用函数。并将副作用函数的返回函数插入新的链表。

      • 更新阶段:执行顺序从上到下。如果碰到useEffect就将其副作用函数传入一个链表,在组件更新之后,找出挂载阶段的返回函数准备执行,如果以来参数改变,返回函数就执行。接着执行更新阶段传入的副作用函数,执行时也要判断依赖参数是否发生了改变。最后将新的返回函数传入新的链表。

      • 卸载阶段:组件即将卸载时,找到更新阶段的组件对应的返回函数,依次执行

      • 依赖为空数组:副作用函数在组件挂载完之后执行,返回函数在组件即将卸载时执行

          useEffect(()=>{
            console.log("组件挂载完之后执行");
            return ()=>{
              console.log("组件即将卸载时执行");
            }
          },[]);
        
      • 没有依赖:组件会在挂载或更新完成之后都会执行

    • useRef()

      • 和createRef类似,都可以用来关联实例
      • useRef(initialValue) :当useRef中存的是一条数据时,该ref中的数据不会随着组件更新而更新,除非手动传入新的数据。所以可以用来做跨组件之间的更新阶段的数据传递。
    • 自定义hook

      • 自定义hook必须使用use开头来命名

      • 当有重复节点时,可以使用自定义hook

      • 通过props来传递可变参数

        import { useState } from "react";
        function useInputText(initialState="",props={}) {
          const [val,setVal] = useState(initialState);
          const input = <input 
              type="text"
              value={val}
              {...props}
              onChange={({target})=>{
                  setVal(target.value);
              }}
           />
          return [val,input,setVal]
        }
        function useTextarea(initialState="",props={}) {
          const [val,setVal] = useState(initialState);
          const input = <textarea
              value={val}
              {...props}
              onChange={({target})=>{
                  setVal(target.value);
              }}
           />
          return [val,input,setVal]
        }
        
        export {useInputText,useTextarea}
        
    • useMemo()

      useMemo会有一个返回值或返回函数,可以用来监听依赖参数的变化。

      function Child() {
        const [nub,setNub] = useState(1);
        const [count,setCount] = useState(1);
        const count2 = useMemo(()=>{ // 返回一个值
          console.log("memo");
          return count*10;
        },[count]);
          
        const clickHandler = useMemo(()=>()=>{ // 返回一个函数
        	alert("点击");
        },[]);
      
        return <>
          <p>{nub}</p>
          <button onClick={()=>{
            setNub(nub + 1);
          }}>递增nub</button>
          <p>{count}</p>
          <p>{count2}</p>
          <button onClick={()=>{
            setCount(count + 1);
          }}>递增count</button>
          <button onClick={clickHandler}>点击</button>
        </>;
      }
      
    • useCallback()

      useMemo的返回值是一个函数时,可以使用useCallback()来替换,useCallback(fn)的参数一一个函数,直接返回,根据依赖参数变化进行触发

      function Child() {
        const clickHandler = useCallback(()=>{
          alert("点击")
        },[])
        return <>
          <button onClick={clickHandler}>点击</button>
        </>;
      }
      
    • memo 高阶组件,会返回一个新的组件

      • 第一个参数是传入组件

      • 第二个参数是一个函数,用来判断前后数据是否一致,如果不一致,则进行更新

        import { memo} from "react";
        function Li({data,setChecked,editMessage}) {
        	***
        }
        const NewLi = memo(Li,(props,nextProps)=>{
          return props.data === nextProps.data;// true 不更新 Li,false 更新 li
        })
        export default NewLi;