Context در React – بخش ۳

در این مقاله به ادامه مبحث Context در React خواهیم پرداخت. مشاهده بخش ۱ ، مشاهده بخش ۲

مصرف چند Context

جهت حفظ سرعت رندر مجدد Context، ری‌اکت نیاز دارد هر مصرف کنندۀ (consumer) Context را به صورت یک node مجزا در درخت درست کند.

// Theme context, default to light theme
const ThemeContext = React.createContext('light');

// Signed-in user context
const UserContext = React.createContext({
  name: 'Guest',
});

class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // App component that provides initial context values
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Layout />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}

function Layout() {
  return (
    <div>
      <Sidebar />
      <Content />
    </div>
  );
}

// A component may consume multiple contexts
function Content() {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

اگر دو مقدار context و یا بیشتر اغلب با هم مورد استفاده قرار می‌گیرند، ممکن است ساخت کامپوننت prop رندر خود را که هر دو را فراهم می‌کند، گزینه خوبی باشد.

دسترسی به context در چرخۀ زندگی

دسترسی به مقادیر context در متد چرخۀ زندگی (lifecycle method) تقریبا به طور رایج مورد استفاده قرار می‌گیرد. به جای اضافه کردن context به متد چرخۀ زندگی، فقط نیاز دارید آن را به عنوان یک prop منتقل کنید و سپس با آن همانطور که به طور معمول با prop کار می‌کنید، کار کنید.

class Button extends React.Component {
  componentDidMount() {
    // ThemeContext value is this.props.theme
  }

  componentDidUpdate(prevProps, prevState) {
    // Previous ThemeContext value is prevProps.theme
    // New ThemeContext value is this.props.theme
  }

  render() {
    const {theme, children} = this.props;
    return (
      <button className={theme || 'light'}>
        {children}
      </button>
    );
  }
}

export default props => (
  <ThemeContext.Consumer>
    {theme => <Button {...props} theme={theme} />}
  </ThemeContext.Consumer>
);

مصرف یک Context با یک HOC

بعضی انواع context به وسیلۀ چند کامپوننت مصرف می‌شوند ( مانند تم). بسته بندی هر وابستگی با یک المان <Context.Consumer> خسته کننده خواهد بود. یک کامپوننت مرتبۀ بالاتر می‌تواند در این موضوع کمک کند.

برای مثال یک کامپوننت button ممکن است یک context تم را مصرف کند:

const ThemeContext = React.createContext('light');

function ThemedButton(props) {
  return (
    <ThemeContext.Consumer>
      {theme => <button className={theme} {...props} />}
    </ThemeContext.Consumer>
  );
}

برای تعدادی کم کامپوننت مشکلی نیست، اما اگر می‌خواستیم از context تم در جاهای زیادی استفاده کنیم چطور؟

می‌توانستیم یک کامپوننت مرتبه بالاتر به نام withTheme بسازیم:

const ThemeContext = React.createContext('light');

// This function takes a component...
export function withTheme(Component) {
  // ...and returns another component...
  return function ThemedComponent(props) {
    // ... and renders the wrapped component with the context theme!
    // Notice that we pass through any additional props as well
    return (
      <ThemeContext.Consumer>
        {theme => <Component {...props} theme={theme} />}
      </ThemeContext.Consumer>
    );
  };
}

حال هر کامپوننت که وابسته به context تم است به راحتی می‌تواند با آن و به وسیلۀ تابع withTheme که ساخته‌ایم، شریک شود:

function Button({theme, ...rest}) {
  return <button className={theme} {...rest} />;
}

const ThemedButton = withTheme(Button);

انتقال Ref ها به مصرف کنندۀ Consumer ها

یک موضوع در رابطه با prop رندر API این است که ref ها به طور خودکار به المان‌های بسته بندی شده منتقل نمی‌شوند. برای حل این مسئله از React.forwardRef استفاده کنید:

fancy-button.js

class FancyButton extends React.Component {
  focus() {
    // ...
  }

  // ...
}

// Use context to pass the current "theme" to FancyButton.
// Use forwardRef to pass refs to FancyButton as well.
export default React.forwardRef((props, ref) => (
  <ThemeContext.Consumer>
    {theme => (
      <FancyButton {...props} theme={theme} ref={ref} />
    )}
  </ThemeContext.Consumer>
));

app.js

import FancyButton from './fancy-button';

const ref = React.createRef();

// Our ref will point to the FancyButton component,
// And not the ThemeContext.Consumer that wraps it.
// This means we can call FancyButton methods like ref.current.focus()
<FancyButton ref={ref} onClick={handleClick}>
  Click me!
</FancyButton>;

هشدارها (Caveat)

از آنجایی که context از هویت مرجع(reference identity) برای تعیین زمان رندر مجدد استفاده می‌کند، گچس‌هایی وجود دارند که می‌توانند هنگام رندر مجدد provider یک parent، رندرهای ناخواسته را در consumer ها به راه اندازند. برای مثال، کد زیر تمام consumer ها را هر بار که Provider رندر مجدد می‌شود، رندر دوباره می‌کند، زیرا همیشه یک آبجکت جدید برای value ساخته می‌شود:

class App extends React.Component {
  render() {
    return (
      <Provider value={{something: 'something'}}>
        <Toolbar />
      </Provider>
    );
  }
}

برای حل این موضوع، value را به داخل حالت parent بکشید:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: {something: 'something'},
    };
  }

  render() {
    return (
      <Provider value={this.state.value}>
        <Toolbar />
      </Provider>
    );
  }
}

میراث API

قبلا React با یک context API تجربی همراه بود. API قدیمی در تمام نسخه‌های ۱۶.x پشتیبانی خواهد شد، اما برنامه‌هایی که از آن استفاده می‌کنند باید به نسخۀ جدید کوچ کنند. میراث API در نسخۀ بزرگ آیندۀ React حذف خواهد شد. مطالب مربوط به نسخه‌های قدیمی و میراث آنها را در این قسمت می‌توانید بخوانید(بزودی).

پایان مبحث Contect

1 Star2 Stars3 Stars4 Stars5 Stars (4 votes, average: 4٫75 out of 5)
Loading...
counter customizable free hit