yaogengzhu / Learning-notes

基础回顾、笔记
1 stars 0 forks source link

React 中不常用的API 掌握 (2022-02-22) #31

Open yaogengzhu opened 2 years ago

yaogengzhu commented 2 years ago

forwardRefuseImperativeHandle

React.forwardRef 会创建一个React组件,这个组件能接受的ref属性转发到其他组件树下的另外一个组件。

两种应用场景

React.forwardRef 接受渲染函数作为参数, React将props 和 ref 作为参数来调用此函数.。此函数应返回React节点。

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// You can now get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

useImperativeHandle 可以让你正在使用ref时自定义暴露给父组件的实例值。这个使用应该与React.forwardRef一起使用。

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

实际应用场景 ant-design-mobile 组件库中 Dialog 方法中 代码地址

import React, {
  createRef,
  forwardRef,
  useEffect,
  useImperativeHandle,
  useState,
} from 'react'
import { renderToBody } from '../../utils/render-to-body'
import { Dialog, DialogProps } from './dialog'

export type DialogShowProps = Omit<DialogProps, 'visible'>

export type DialogShowRef = {
  close: () => void
}

export function show(props: DialogShowProps) {
  const Wrapper = forwardRef<DialogShowRef>((_, ref) => {
    const [visible, setVisible] = useState(false)
    useEffect(() => {
      setVisible(true)
    }, [])
    function handleClose() {
      props.onClose?.()
      setVisible(false)
    }
    useImperativeHandle(ref, () => ({
      close: handleClose,
    }))
    return (
      <Dialog
        {...props}
        visible={visible}
        onClose={handleClose}
        afterClose={() => {
          props.afterClose?.()
          unmount()
        }}
      />
    )
  })
  const ref = createRef<DialogShowRef>()
  const unmount = renderToBody(<Wrapper ref={ref} />)
  return {
    close: () => {
      ref.current?.close()
    },
  }
}
yaogengzhu commented 2 years ago

useContext 和 Context

useContext 接收一个context对象 React.createContext的返回值 并返回该context的当前值,当前的context值由上层组件中距离当前组件最近的 的value prop 决定。

useContext的参数必须是context对象本身

context = React.createContext({})
const { } = useContext(context)

Context提供了一个无需为每层组件手动添加props,就能在组件进行数据传递。 Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递props

API React.createContext

const MyContext = React.createContext(defaultValue);

Context.Provider

<MyContext.Provider value={/* 某个值 */}>

Context.Consumer

<MyContext.Consumer>
  {value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>

Context.displayName 用来在 React DevTools 使用该字符串来确定context的要显示的内容

const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';

<MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中
<MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中

实践 --- 国际化

import { createContext } from 'react';

export type ILang = 'zh-CN' | 'en-US'
type IContentProps = {
  lang: ILang;
  setLang: (value: ILang) => void;
};
export const GlobalContext = createContext<IContentProps>({
  lang: 'zh-CN',
  setLang: () => {}
});
const App = () => {
  useTheme();
  const [lang, setLang] = useState<ILang>('zh-CN'); // 后期做一个本地持久化
  const contextValue = {
    lang,
    setLang,
  };

  const locale = useMemo(() => {
    switch (lang) {
      case 'zh-CN':
        return zhCN;
      case 'en-US':
        return enUS;
      default:
        return zhCN;
    }
  }, [lang]);

  return (
    <ConfigProvider locale={locale}>
      <GlobalContext.Provider value={contextValue}>
        <BrowserRouter>
          <Suspense fallback={<Loading />}>
            <Routes>
              <Route path="/login" element={<Login />} />
              <Route path="/*" element={<Home />} />
            </Routes>
          </Suspense>
        </BrowserRouter>
      </GlobalContext.Provider>
    </ConfigProvider>
  );
};

使用

const { lang, i18n, setLang } = useLocale();

...
  const changeLanguage = (lang: string) => {
    if (lang === 'zh-CN') {
      Message.info('语言切换至 zh-CN');
      setLang('zh-CN');
    } else {
      Message.info('Language switch to en-US');
      setLang && setLang('en-US');
    }
  };

  const languageList = (
    <Menu onClickMenuItem={changeLanguage} defaultSelectedKeys={[lang]}>
      <Menu.Item key="zh-CN">中文</Menu.Item>
      <Menu.Item key="en">English</Menu.Item>
    </Menu>
  );
...
yaogengzhu commented 2 years ago

补充文档: 对上面文档进行补充:

react 文档 如何访问另一个组件的DOM节点 Accessing another component’s DOM nodes

import { useRef, forwardRef } from 'react'
import TodoList from './components/TodoList'

const MyInput = forwardRef((props, ref: React.LegacyRef<HTMLInputElement>) => {
  return <input {...props} ref={ref} />
})

const App = () => {
  const inputRef: React.LegacyRef<HTMLInputElement> = useRef(null)
  const focusEvent = () => {
    inputRef.current?.focus()
  }
  return (
    <div title="test" data-test="container">
      <TodoList />
      <MyInput ref={inputRef} />
      <button onClick={focusEvent}>按钮</button>
    </div>
  )
}

export default App