React组件的组合和复用方式总结 | 掘金技术征文-双节特别篇

1,068 阅读2分钟

前言

React的设计思想在很多地方体现了函数式编程的方式,各组件之间组合和复用的方式有多重,这里总结我日常开发中所用到的几种。

高阶组件

抛开React,作为JavaScript开发者,高阶函数应该是有很多接触,例如常见的防抖与节流(防抖与节流)。如果非要给他一个定义的话呢,我们还是看官方吧,高阶组件就是传入组件返回也是组件的函数 高阶组件

开发中经常会遇到渲染列表,若数据为空的时候显示为空的需求,如果是单个组件需要呢没关系,加个判断就好

render() {
	if (_.isEmpty(this.prop.dataSource)) {
    	return <Empty />;
    }
    .....
}

如果是多个这样的判断的话呢,代码重复率就会很高,这个时候我们就可以考虑用高阶组件,将为空的判断抽离为一个高阶组件

const withEmpty = (WrappedComponent) => {
  return (props) => {
    return _.isEmpty(props.dataSource)
      ? <Empty />
      : <WrappedComponent {...props} />;
  }
}

使用

render() {
	const WithEmptyUserList = withEmpty(UserList);
	return <WithEmptyUserList dataSource={[]}/>;
}

相关部分官方给出了很多说明,可以参考高阶组件

render prop

在调用组件时,引入一个函数类型的 prop,这个 prop 定义了组件的渲染方式,当然这个 prop 不是必须得叫 render

const Mouse = ({ render }) => {
  const [x, setX] = useState(0);
  const handleMouseMove = (e) => {
    setX(e.pageX);
  }
  return <div onMouseMove={handleMouseMove}>
    {render({ x })}
  </div>
}

class App extends Component {
  constructor(props) {
    super(props)
  }
  render() {
    return (
      <Mouse render={({ x }) => <p>{x}</p>} />
    )
  }
}

Mouse 组件记录了移动的x坐标,渲染工作交由父组件传入的 render 函数。

Child Component

这在一些react公共组件里面很常见,比如 Contextreact-router

// Context
render() {
	return <Context.Consumer>
    	{(props) => {...}}
    </Context.Consumer>
}


// React-router
render() {
  return (
    <HashRouter>
      <Route path='/' render={() => '/'} />
      <Route path='/home' render={() => '/home'} />
    </HashRouter>
  )
}

市面上有很多的公共组件都采用了类似的封装方式,还有比如 antd 的多个组件

这里实现了一个超懒版本的 react-routerhashRouterRoute 的部分

const Context = createContext();
const HashRouter = ({ children }) => {
  const [match, setMatch] = useState(_.replace(location.hash, '#', ''));
  useEffect(() => {
    const handleHashChange = (value) => {
      setMatch(_.replace(location.hash, '#', ''));
    }
    window.addEventListener('hashchange', handleHashChange);

    return () => window.removeEventListener('hashchange', handleHashChange);
  }, []);
  return <Context.Provider value={{ match }}>
    {children}
  </Context.Provider>
}

const Route = ({ path, render }) => {
  return <Context.Consumer>
    {({match}) => {
      if (match === path) {
        return render()
      };
      return null;
    }}
  </Context.Consumer>
}

class App extends Component {
  constructor(props) {
    super(props)
  }
  render() {
    return (
      <HashRouter>
        <Route path='/' render={() => '/'} />
        <Route path='/home' render={() => '/home'} />
      </HashRouter>
    )
  }
}

关键两步

  • HashRouter 监听路由变化,保留 hash 值,渲染所有的 children
  • Route 中对比当前的 hash 和传入的 path,如果正确则返回

真正的 react-router 相比这里多了很多判断,比如这里只是对 hash 值进行全等操作,实际上 Route 的判断要复杂得多,以下附上 Route 部分源码

{props.match
  ? children
    ? typeof children === "function"
      ? __DEV__
        ? evalChildrenDev(children, props, this.props.path)
        : children(props)
      : children
    : component
    ? React.createElement(component, props)
    : render
    ? render(props)
    : null
  : typeof children === "function"
  ? __DEV__
    ? evalChildrenDev(children, props, this.props.path)
    : children(props)
  : null}

推荐自行阅读相关源码

总结

文章的很多代码使用的 hook 所有16.8 以下的版本可能会有问题,以上总结基于本人日常工作中常用几种复用方式,欢迎讨论

🏆 掘金技术征文|双节特别篇