内容简介:在使用React开发相关页面或维护相关组件时,常常会遇到一些难以避免的问题,比如:为了解决这类问题,目前常见的解决方法为render props 与基于上述原因,react团队在React
引言 & 背景
在使用React开发相关页面或维护相关组件时,常常会遇到一些难以避免的问题,比如:
- 组件中与状态相关的逻辑很难复用,例如相似弹层组件的打开、关闭、loading等
- 逻辑复杂的组件 难以开发与维护,当我们的组件需要处理多个互不相关的 local state 时,每个生命周期函数中可能会包含着各种互不相关的逻辑在里面。
- class组件中的this增加学习成本,class组件在基于现有 工具 的优化上存在些许问题。
- 由于业务变动,函数组件不得不改为类组件等等。
为了解决这类问题,目前常见的解决方法为render props 与 higher-order components ,但这两种方式可能会很笨重,而且会导致JSX嵌套过深。
// 普通方法 import React from 'react' import ReactDOM from 'react-dom' const App = React.createClass({ getInitialState() { return { x: 0, y: 0 } }, handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }) }, render() { const { x, y } = this.state return ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> <h1>The mouse position is ({x}, {y})</h1> </div> ) } }) ReactDOM.render(<App/>, document.getElementById('app')) 复制代码
// mixin // 缺点ES6 classes,state修改来源很难追踪,多个mixin名称冲突 import React from 'react' import ReactDOM from 'react-dom' // This mixin contains the boilerplate code that // you'd need in any app that tracks the mouse position. // We can put it in a mixin so we can easily share // this code with other components! const MouseMixin = { getInitialState() { return { x: 0, y: 0 } }, handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }) } } const App = React.createClass({ // Use the mixin! mixins: [ MouseMixin ], render() { const { x, y } = this.state return ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> <h1>The mouse position is ({x}, {y})</h1> </div> ) } }) ReactDOM.render(<App/>, document.getElementById('app')) 复制代码
// HOC // 缺点JSX嵌套,props来源很难确定,多个HOC导致props冲突以及props传递问题 const withMouse = (Component) => { return class extends React.Component { state = { x: 0, y: 0 } handleMouseMove = (event) => { this.setState({ x: event.clientX, y: event.clientY }) } render() { return ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> <Component {...this.props} mouse={this.state}/> </div> ) } } } const App = React.createClass({ render() { // Instead of maintaining our own state, // we get the mouse position as a prop! const { x, y } = this.props.mouse return ( <div style={{ height: '100%' }}> <h1>The mouse position is ({x}, {y})</h1> </div> ) } }) // Just wrap your component in withMouse and // it'll get the mouse prop! const AppWithMouse = withMouse(App) 复制代码
import React from 'react' import ReactDOM from 'react-dom' import PropTypes from 'prop-types' // Instead of using a HOC, we can share code using a // regular component with a render prop! class Mouse extends React.Component { static propTypes = { render: PropTypes.func.isRequired } state = { x: 0, y: 0 } handleMouseMove = (event) => { this.setState({ x: event.clientX, y: event.clientY }) } render() { return ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> {this.props.render(this.state)} </div> ) } } const App = React.createClass({ render() { return ( <div style={{ height: '100%' }}> <Mouse render={({ x, y }) => ( // The render prop gives us the state we need // to render whatever we want here. <h1>The mouse position is ({x}, {y})</h1> )}/> </div> ) } }) ReactDOM.render(<App/>, document.getElementById('app')) // render props -> hoc const withMouse = (Component) => { return class extends React.Component { render() { return <Mouse render={mouse => ( <Component {...this.props} mouse={mouse}/> )}/> } } } 复制代码
React Hooks概述
基于上述原因,react团队在React 16.7.0-alpha
推出了React Hooks这一新的特性。
首先用一段简单的代码介绍一下什么是React Hooks
import { useState, useEffect } from 'react'; function Example() { // Declare a new state variable, which we'll call "count" const [count, setCount] = useState(0); // Similar to componentDidMount and componentDidUpdate: useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } 复制代码
什么是React Hooks? React Hooks是一种React中使用的特殊的函数,例如**useState**可以使我们在React函数组件中使用state,**useEffect**可以使我们在函数式组件中处理副作用。
React Hooks 带来的好处不仅是 “更 FP,更新粒度更细,代码更清晰”,还有如下三个特性:
render
React Hooks生命周期
useState 什么时候执行? 它会在组件每次render的时候调用
useEffect 什么时候执行? 它会在组件 mount 和 unmount 以及每次重新渲染的时候都会执行,也就是会在 componentDidMount、componentDidUpdate、componentWillUnmount 这三个时期执行。
清理函数(clean up)什么时候执行? 它会在前一次 effect执行后,下一次 effect 将要执行前,以及 Unmount 时期执行
React Hooks 组合与自定义Hooks
import { useState, useEffect } from "react"; // 底层 Hooks, 返回布尔值:是否在线 function useFriendStatusBoolean(friendID) { const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) { setIsOnline(status.isOnline); } useEffect(() => { ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; } // 上层 Hooks,根据在线状态返回字符串:Loading... or Online or Offline function useFriendStatusString(props) { const isOnline = useFriendStatusBoolean(props.friend.id); if (isOnline === null) { return "Loading..."; } return isOnline ? "Online" : "Offline"; } // 使用了底层 Hooks 的 UI function FriendListItem(props) { const isOnline = useFriendStatusBoolean(props.friend.id); return ( <li style={{ color: isOnline ? "green" : "black" }}>{props.friend.name}</li> ); } // 使用了上层 Hooks 的 UI function FriendListStatus(props) { const statu = useFriendStatusString(props.friend.id); return <li>{statu}</li>; } 复制代码
React Hooks 应用举例
利用 useState 创建 Redux
// 这就是 Redux function useReducer(reducer, initialState) { const [state, setState] = useState(initialState); function dispatch(action) { const nextState = reducer(state, action); setState(nextState); } return [state, dispatch]; } // 一个 Action function useTodos() { const [todos, dispatch] = useReducer(todosReducer, []); function handleAddClick(text) { dispatch({ type: "add", text }); } return [todos, { handleAddClick }]; } // 绑定 Todos 的 UI function TodosUI() { const [todos, actions] = useTodos(); return ( <> {todos.map((todo, index) => ( <div>{todo.text}</div> ))} <button onClick={actions.handleAddClick}>Add Todo</button> </> ); } 复制代码
利用 hooks取代生命周期
// react调用g2 class Component extends React.PureComponent<Props, State> { private chart: G2.Chart = null; private rootDomRef: React.ReactInstance = null; componentDidMount() { this.rootDom = ReactDOM.findDOMNode(this.rootDomRef) as HTMLDivElement; this.chart = new G2.Chart({ container: document.getElementById("chart"), forceFit: true, height: 300 }); this.freshChart(this.props); } componentWillReceiveProps(nextProps: Props) { this.freshChart(nextProps); } componentWillUnmount() { this.chart.destroy(); } freshChart(props: Props) { // do something this.chart.render(); } render() { return <div ref={ref => (this.rootDomRef = ref)} />; } } // hooks调用g2 function App() { const ref = React.useRef(null); let chart: G2.Chart = null; React.useEffect(() => { if (!chart) { chart = new G2.Chart({ container: ReactDOM.findDOMNode(ref.current) as HTMLDivElement, width: 500, height: 500 }); } // do something chart.render(); return () => chart.destroy(); }); return <div ref={ref} />; } 复制代码
修改页面 title
function useDocumentTitle(title) { useEffect( () => { document.title = title; return () => (document.title = "大宝"); }, [title] ); } // 使用 useDocumentTitle("包裹复核"); 复制代码
监听页面大小变化
function getSize() { return { innerHeight: window.innerHeight, innerWidth: window.innerWidth, outerHeight: window.outerHeight, outerWidth: window.outerWidth }; } function useWindowSize() { let [windowSize, setWindowSize] = useState(getSize()); function handleResize() { setWindowSize(getSize()); } useEffect(() => { window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, []); return windowSize; } // 使用 const windowSize = useWindowSize(); return <div>页面高度:{windowSize.innerWidth}</div>; 复制代码
其他
- DOM 副作用修改 / 监听。
- React组件辅助函数。
- 处理动画相关逻辑。
- 处理发送请求。
- 处理表单填写。
- 存数据。
注意
- 只能在顶层代码(Top Level)中调用 Hook
- 不要在循环,条件判断,嵌套函数里面调用 Hooks
- 只在 React 的函数里面调用 Hooks
- 命名时使用use*命名Hooks
- 使用 eslint-plugin-react-hooks 插件进行检查
参考文档
- React官方文档
- 精读《React Hooks》
- 精读《怎么用 React Hooks 造轮子》
- 10分钟了解 react 引入的 Hooks
- [ 译] React hooks: 不是魔法,只是数组
- Use a Render Prop!
码字不易,如有建议请扫码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。