重生之我又来学React了Day02 -- Ref和Context

151 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

接着上一篇的内容,今天我们复习React基础之Ref,Context

image.png

Ref

使用ref我们可以操作DOM元素或组件。

如何创建ref对象:

React.createRef()

该方法只能在类组件中使用

比如点击按钮获取某个DOM元素

class Child extends React.Component {
    constructor() {
        super();
        this.divRef = React.createRef();
        this.state = {
        books: "三国演义",
        age: "20"
    };
}

showDivRef = () => {
    console.log(this.divRef.current); // <div id="book">Jack给的书:三国演义  </div>
};

render() {
    return (
    <div>
        <div ref={this.divRef} id="book">
        Jack给的书:{this.state.books}
        </div>
        <button onClick={this.showDivRef}>展示Ref</button>
    </div>
    );
    }
}

useRef()

该hook是针对函数组件使用的,类组件使用React.createRef()。

比如获取某个组件

function FnChild() {
  const currentDom = React.useRef(null);
  const getRef = () => {
    console.log(currentDom.current);
  };
  return (
    <div>
      <p>我是函数组件</p>
      <Child ref={currentDom}></Child>
      <button onClick={getRef}>按钮2</button>
    </div>
  );
}

打印结果: image.png

⚠️React.createRef()也可以在函数组件中使用。
只不过React.createRef创建的引用不能保证每次重新渲染后引用固定不变。如果你只是使用React.createRef“勾住”JSX组件转换后对应的真实DOM对象是没问题的,但是如果想“勾住”在useEffect中创建的变量,那是做不到的。

ref属性有3种用法

字符串的形式
class Child extends React.Component {
  constructor() {
    super();
    this.state = {
      books: "三国演义",
      age: "20"
    };
  }

  showDivRef = () => {
     // 只有字符串的形式是通过refs来获取的
    console.log(this.refs); // {book: HTMLDivElement, btn: HTMLButtonElement} 
  };

  render() {
    return (
      <div>
        <div ref="book" id="book">
          Jack给的书:{this.state.books}
        </div>
        <button onClick={this.showDivRef} ref="btn">
          展示Refbook
        </button>
        {/* <FnChild ref="fn"></FnChild> */}
      </div>
    );
  }
}

⚠️字符串的形式,控制台会给出警告让你使用 useRef()或者createRef()

ref属性是一个函数

当真实DOM创建完成之后,通过callback的形式,回调函数的第一个参数即是真实DOM或组件实例。在函数组件中使用的话,就给出警告让你使用useRef

class Child extends React.Component {
  constructor() {
    super();
    this.state = {
      books: "三国演义",
      age: "20"
    };
    this.ref1 = null;
    this.ref2 = null;
  }

  showDivRef = () => {
    console.log(this.refs); // {}
    console.log(this.ref1); // <div id="book">Jack给的书:三国演义</div>
    console.log(this.ref2); // <button>展示Refbook</button>
  };

  render() {
    return (
      <div>
        <div ref={(arg) => (this.ref1 = arg)} id="book">
          Jack给的书:{this.state.books}
        </div>
        <button onClick={this.showDivRef} ref={(arg) => (this.ref2 = arg)}>
          展示Refbook
        </button>
      </div>
    );
  }
}
ref属性是一个对象

上面已经介绍过了

Ref对象其实就是一个带有current属性的对象。

export function createRef() { 
  const refObject = { current: null, } 
  return refObject; 
}

ref的高阶用法

forwardRef

顾名思义,就是转发Ref, 可跨组件获取ref对象(孙组件获取到爷组件的ref对象,即可以在爷组件中可以获取到子组件的DOM或组件等)

export default function App() {
    const [currentRef, setCurrentRef] = useState(null);
    const getRef = () => {
      console.log(currentRef); // <span>我是子组件</span>
    };
    return (
      <div className="App">
        <button onClick={getRef}>获取子组件的ref</button>
        <NewParent ref={(node) => setCurrentRef(node)}></NewParent>
      </div>
    );
  }
  
  // 获取到在爷组件定义的ref对象,就可以将子组件某元素或组件放入爷组件的ref对象中
  const NewParent = React.forwardRef((props, ref) => (
    <Parent AppRef={ref} {...props} />
  ));
  
  function Parent(props) {
    return (
      <div>
        父组件
        <Son AppRef={props.AppRef}></Son>
      </div>
    );
  }
  
  function Son(props) {
    const { AppRef } = props;
    return (
      <div>
        <span ref={AppRef}>我是子组件</span>
        {/* <Son2 ref={AppRef}></Son2> */}
      </div>
    );
  }
  
  

Context

父子组件可以通过props传递数据,但是如果是多层级的话,会很不方便。这个时候就应该考虑使用context了。 实现跨层级传递数据,比如本地语言环境等

Provider提供context,provider中value属性改变会使所有context中的consumer组件更新。

使用方式:

React.createContext()

const Context = React.createContext(null)

export default function App() {
    return (
      <Context.Provider value={{ locale: "de" }}>
        <Son></Son>
      </Context.Provider>
    );
  }
  
  function Son(props) {
    return (
      <Context.Consumer>
        {(val) => {
          return <span>{val.locale}</span>; // de
        }}
      </Context.Consumer>
    );
  }

Privider逐层传递

Provider可以逐层传递context,下一层级的Provider会覆盖上一层级的Provider。React-redux中connect就是利用这个特性传递Provider的。

const Context = React.createContext(null);
function Son2() {
  return (
    <Context.Consumer>
      {(context) => {
        return <div className="sonbox"> 第二层role:{context.role}</div>;
      }}
    </Context.Consumer>
  );
}
function Son() {
  const { role } = React.useContext(Context);
  const [context] = React.useState({ role: "user" });
  /* 第二层 Provder 传递内容 */
  return (
    <div className="box">
      第一层role:{role}
      <Context.Provider value={context}>
        <Son2 />
      </Context.Provider>
    </div>
  );
}

export default function Demo() {
  const [themeContextValue] = React.useState({ role: "admin" });
  /* 第一层  Provider 传递内容  */
  return (
    <Context.Provider value={themeContextValue}>
      <Son />
    </Context.Provider>
  );
}

image.png