摘要:返回元素的是将新的与原始元素的浅层合并后的结果。生命周期方法要如何对应到函数组件不需要构造函数。除此之外,可以认为的设计在某些方面更加高效避免了需要的额外开支,像是创建类实例和在构造函数中绑定事件处理器的成本。
React系列
React系列 --- 简单模拟语法(一)
React系列 --- Jsx, 合成事件与Refs(二)
React系列 --- virtualdom diff算法实现分析(三)
React系列 --- 从Mixin到HOC再到HOOKS(四)
React系列 --- createElement, ReactElement与Component部分源码解析(五)
React系列 --- 从使用React了解Css的各种使用方案(六)
这是React初期提供的一种组合方案,通过引入一个公用组件,然后可以应用公用组件的一些生命周期操作或者定义方法,达到抽离公用代码提供不同模块使用的目的.
曾经的官方文档demo如下
var SetIntervalMixin = { componentWillMount: function() { this.intervals = []; }, setInterval: function() { this.intervals.push(setInterval.apply(null, arguments)); }, componentWillUnmount: function() { this.intervals.map(clearInterval); }, }; var TickTock = React.createClass({ mixins: [SetIntervalMixin], // Use the mixin getInitialState: function() { return { seconds: 0 }; }, componentDidMount: function() { this.setInterval(this.tick, 1000); // Call a method on the mixin }, tick: function() { this.setState({ seconds: this.state.seconds + 1 }); }, render: function() { returnReact has been running for {this.state.seconds} seconds.
; }, }); React.render(, document.getElementById("example"));
但是Mixins只能应用在createClass的创建方式,在后来的class写法中已经被废弃了.原因在于:
mixin引入了隐式依赖关系
不同mixins之间可能会有先后顺序甚至代码冲突覆盖的问题
mixin代码会导致滚雪球式的复杂性
详细介绍mixin危害性文章可直接查阅Mixins Considered Harmful
高阶组件(Higher-order component)HOC是一种React的进阶使用方法,大概原理就是接收一个组件然后返回一个新的继承组件,继承方式分两种
属性代理(Props Proxy)最基本的实现方式
function PropsProxyHOC(WrappedComponent) { return class NewComponent extends React.Component { render() { return} } }
从代码可以看出属性代理方式其实就是接受一个 WrappedComponent 组件作为参数传入,并返回一个继承了 React.Component 组件的类,且在该类的 render() 方法中返回被传入的 WrappedComponent 组件
抽离state && 操作propsfunction PropsProxyHOC(WrappedComponent) { return class NewComponent extends React.Component { constructor(props) { super(props); this.state = { name: "PropsProxyHOC", }; } logName() { console.log(this.name); } render() { const newProps = { name: this.state.name, logName: this.logName, }; return; } }; } class Main extends Component { componentDidMount() { this.props.logName(); } render() { return PropsProxyHOC; } } export default PropsProxyHOC(Main);
demo代码可以参考这里
有种常见的情况是用来做双向绑定
function PropsProxyHOC(WrappedComponent) { return class NewComponent extends React.Component { constructor(props) { super(props); this.state = { fields: {} }; } getField(fieldName) { const _s = this.state; if (!_s.fields[fieldName]) { _s.fields[fieldName] = { value: "", onChange: event => { this.state.fields[fieldName].value = event.target.value; // 强行触发render this.forceUpdate(); console.log(this.state); }, }; } return { value: _s.fields[fieldName].value, onChange: _s.fields[fieldName].onChange, }; } render() { const newProps = { fields: this.getField.bind(this), }; return; } }; } // 被获取ref实例组件 class Main extends Component { render() { return ; } } export default PropsProxyHOC(Main);
demo代码可以参考这里
获取被继承refs实例因为这是一个被HOC包装过的新组件,所以想要在HOC里面获取新组件的ref需要用些特殊方式,但是不管哪种,都需要在组件挂载之后才能获取到.并且不能在无状态组件(函数类型组件)上使用 ref 属性,因为无状态组件没有实例。
通过父元素传递方法获取function PropsProxyHOC(WrappedComponent) { return class NewComponent extends React.Component { render() { const newProps = {}; // 监听到有对应方法才生成props实例 typeof this.props.getInstance === "function" && (newProps.ref = this.props.getInstance); return; } }; } // 被获取ref实例组件 class Main extends Component { render() { return Main; } } const HOCComponent = PropsProxyHOC(Main); class ParentComponent extends Component { componentWillMount() { console.log("componentWillMount: ", this.wrappedInstance); } componentDidMount() { console.log("componentDidMount: ", this.wrappedInstance); } // 提供给高阶组件调用生成实例 getInstance(ref) { this.wrappedInstance = ref; } render() { return; } } export default ParentComponent;
demo代码可以参考这里
通过高阶组件当中间层相比较上一方式,需要在高阶组件提供设置赋值函数,并且需要一个props属性做标记
function PropsProxyHOC(WrappedComponent) { return class NewComponent extends React.Component { // 返回ref实例 getWrappedInstance = () => { if (this.props.withRef) { return this.wrappedInstance; } }; //设置ref实例 setWrappedInstance = ref => { this.wrappedInstance = ref; }; render() { const newProps = {}; // 监听到有对应方法才赋值props实例 this.props.withRef && (newProps.ref = this.setWrappedInstance); return; } }; } // 被获取ref实例组件 class Main extends Component { render() { return Main; } } const HOCComponent = PropsProxyHOC(Main); class ParentComponent extends Component { componentWillMount() { console.log("componentWillMount: ", this.refs.child); } componentDidMount() { console.log("componentDidMount: ", this.refs.child.getWrappedInstance()); } render() { return; } } export default ParentComponent;
demo代码可以参考这里
forwardRefReact.forwardRef 会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。这种技术并不常见,但在以下两种场景中特别有用:
转发 refs 到 DOM 组件
在高阶组件中转发 refs
const FancyButton = React.forwardRef((props, ref) => ( )); // You can now get a ref directly to the DOM button: const ref = React.createRef();Click me! ;
以下是对上述示例发生情况的逐步解释:
我们通过调用 React.createRef 创建了一个 React ref 并将其赋值给 ref 变量。
我们通过指定 ref 为 JSX 属性,将其向下传递给
React 传递 ref 给 fowardRef 内函数 (props, ref) => ...,作为其第二个参数。
我们向下转发该 ref 参数到 ,将其指定为 JSX 属性。
当 ref 挂载完成,ref.current 将指向 DOM 节点。
劫持渲染最简单的例子莫过于loading组件了
function PropsProxyHOC(WrappedComponent) { return class NewComponent extends React.Component { render() { return this.props.isLoading ?Loading...:; } }; } // 被获取ref实例组件 class Main extends Component { render() { return Main; } } const HOCComponent = PropsProxyHOC(Main); class ParentComponent extends Component { constructor() { super(); this.state = { isLoading: true, }; } render() { setTimeout(() => this.setState({ isLoading: false }), 2000); return; } } export default ParentComponent;
当然也能用于布局上嵌套在其他元素输出
demo代码可以参考这里
最简单的demo代码
function InheritanceInversionHOC(WrappedComponent) { return class NewComponent extends WrappedComponent { render() { return super.render(); } }; }
在这里WrappedComponent成了被继承的那一方,从而可以在高阶组件中获取到传递组件的所有相关实例
获取继承组件实例function InheritanceInversionHOC(WrappedComponent) { return class NewComponent extends WrappedComponent { componentDidMount() { console.log("componentDidMount: ", this); } render() { return super.render(); } }; } // 被获取ref实例组件 class Main extends Component { constructor() { super(); this.state = { name: "WrappedComponent", }; } render() { returnMain; } } export default InheritanceInversionHOC(Main);
demo代码可以参考这里
修改props和劫持渲染再讲解demo之前先科普React的一个方法
React.cloneElement( element, [props], [...children] )
以 element 元素为样板克隆并返回新的 React 元素。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。新的子元素将取代现有的子元素,而来自原始元素的 key 和 ref 将被保留。
React.cloneElement() 几乎等同于:
{children}
但是,这也保留了组件的 ref。这意味着当通过 ref 获取子节点时,你将不会意外地从你祖先节点上窃取它。相同的 ref 将添加到克隆后的新元素中。
相比属性继承来说,反向继承修改props会比较复杂一点
function InheritanceInversionHOC(WrappedComponent) { return class NewComponent extends WrappedComponent { constructor() { super(); this.state = { a: "b", }; } componentDidMount() { console.log("componentDidMount: ", this); } render() { const wrapperTree = super.render(); const newProps = { name: "NewComponent", }; const newTree = React.cloneElement(wrapperTree, newProps, wrapperTree.props.children); console.log("newTree: ", newTree); return newTree; } }; } // 被获取ref实例组件 class Main extends Component { render() { returnMain; } } export default InheritanceInversionHOC(Main);
demo代码可以参考这里
为什么需要用到cloneElement方法?因为render函数内实际上是调用React.creatElement产生的React元素,尽管我们可以拿到这个方法但是无法修改它.可以用getOwnPropertyDescriptors查看它的配置项
demo代码可以参考这里
本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。
你应该熟悉 ref 这一种访问 DOM 的主要方式。如果你将 ref 对象以
不要在循环,条件,或者内嵌函数中调用.这都是为了保证你的代码在每次组件render的时候会按照相同的顺序执行HOOKS,而这也是能够让React在多个useState和useEffect执行中正确保存数据的原因
只在React函数调用HOOKSReact函数组件调用
从自定义HOOKS中调用
可以确保你源码中组件的所有有状态逻辑都是清晰可见的.
自定义HOOKS我们可以将相关逻辑抽取出来
function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) { setIsOnline(status.isOnline); } useEffect(() => { ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }
我必须以“use”开头为自定义钩子命名吗? 这项公约非常重要。如果没有它,我们就不能自动检查钩子是否违反了规则,因为我们无法判断某个函数是否包含对钩子的调用。
使用相同钩子的两个组件是否共享状态? 不。自定义钩子是一种重用有状态逻辑的机制(例如设置订阅并记住当前值),但是每次使用自定义钩子时,其中的所有状态和效果都是完全隔离的。
自定义钩子如何获得隔离状态? 对钩子的每个调用都处于隔离状态。从React的角度来看,我们的组件只调用useState和useEffect。
问题 Hook 会替代 render props 和高阶组件吗?通常,render props 和高阶组件只渲染一个子节点。我们认为让 Hook 来服务这个使用场景更加简单。这两种模式仍有用武之地,(例如,一个虚拟滚动条组件或许会有一个 renderItem 属性,或是一个可见的容器组件或许会有它自己的 DOM 结构)。但在大部分场景下,Hook 足够了,并且能够帮助减少嵌套。
生命周期方法要如何对应到 Hook?constructor:函数组件不需要构造函数。你可以通过调用 useState 来初始化 state。如果计算的代价比较昂贵,你可以传一个函数给 useState。
getDerivedStateFromProps:改为在渲染时安排一次更新。
shouldComponentUpdate:详见 React.memo.
render:这是函数组件体本身。
componentDidMount, componentDidUpdate, componentWillUnmount:useEffect Hook 可以表达所有这些的组合。
componentDidCatch and getDerivedStateFromError:目前还没有这些方法的 Hook 等价写法,但很快会加上。
我可以只在更新时运行 effect 吗?这是个比较罕见的使用场景。如果你需要的话,你可以 使用一个可变的 ref 手动存储一个布尔值来表示是首次渲染还是后续渲染,然后在你的 effect 中检查这个标识。
如何获取上一轮的 props 或 state?目前,你可以通过ref来手动实现:
function Counter() { const [count, setCount] = useState(0); const prevCount = usePrevious(count); return (有类似 forceUpdate 的东西吗?Now: {count}, before: {prevCount}
); } function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }); return ref.current; }
如果前后两次的值相同,useState 和 useReducer Hook 都会放弃更新。原地修改 state 并调用 setState 不会引起重新渲染。
通常,你不应该在 React 中修改本地 state。然而,作为一条出路,你可以用一个增长的计数器来在 state 没变的时候依然强制一次重新渲染:
const [ignored, forceUpdate] = useReducer(x => x + 1, 0); function handleClick() { forceUpdate(); }我该如何测量 DOM 节点?
要想测量一个 DOM 节点的位置或是尺寸,你可以使用 callback ref。每当 ref 被附加到另一个节点,React 就会调用 callback。
function MeasureExample() { const [rect, ref] = useClientRect(); return (); } function useClientRect() { const [rect, setRect] = useState(null); const ref = useCallback(node => { if (node !== null) { setRect(node.getBoundingClientRect()); } }, []); return [rect, ref]; }Hello, world
{rect !== null &&The above header is {Math.round(rect.height)}px tall
}
demo代码可以参考这里
使用 callback ref 可以确保 即便子组件延迟显示被测量的节点 (比如为了响应一次点击),我们依然能够在父组件接收到相关的信息,以便更新测量结果。
注意到我们传递了 [] 作为 useCallback 的依赖列表。这确保了 ref callback 不会在再次渲染时改变,因此 React 不会在非必要的时候调用它。
我该如何实现 shouldComponentUpdate?你可以用 React.memo 包裹一个组件来对它的 props 进行浅比较:
const Button = React.memo((props) => { // 你的组件 });
React.memo 等效于 PureComponent,但它只比较 props。(你也可以通过第二个参数指定一个自定义的比较函数来比较新旧 props。如果函数返回 true,就会跳过更新。)
React.memo 不比较 state,因为没有单一的 state 对象可供比较。但你也可以让子节点变为纯组件,或者 用useMemo优化每一个具体的子节点。
如何惰性创建昂贵的对象?第一个常见的使用场景是当创建初始 state 很昂贵时,为避免重新创建被忽略的初始 state,我们可以传一个函数给 useState,React 只会在首次渲染时调用这个函数
function Table(props) { // createRows() 只会被调用一次 const [rows, setRows] = useState(() => createRows(props.count)); // ... }
你或许也会偶尔想要避免重新创建 useRef() 的初始值。useRef 不会像 useState 那样接受一个特殊的函数重载。相反,你可以编写你自己的函数来创建并将其设为惰性的:
function Image(props) { const ref = useRef(null); // IntersectionObserver 只会被惰性创建一次 function getObserver() { let observer = ref.current; if (observer !== null) { return observer; } let newObserver = new IntersectionObserver(onIntersect); ref.current = newObserver; return newObserver; } // 当你需要时,调用 getObserver() // ... }Hook 会因为在渲染时创建函数而变慢吗?
不会。在现代浏览器中,闭包和类的原始性能只有在极端场景下才会有明显的差别。
除此之外,可以认为 Hook 的设计在某些方面更加高效:
Hook 避免了 class 需要的额外开支,像是创建类实例和在构造函数中绑定事件处理器的成本。
符合语言习惯的代码在使用 Hook 时不需要很深的组件树嵌套。这个现象在使用高阶组件、render props、和 context 的代码库中非常普遍。组件树小了,React 的工作量也随之减少。
传统上认为,在 React 中使用内联函数对性能的影响,与每次渲染都传递新的回调会如何破坏子组件的 shouldComponentUpdate 优化有关。Hook 从三个方面解决了这个问题。
useCallback Hook 允许你在重新渲染之间保持对相同的回调引用以使得 shouldComponentUpdate 继续工作:
useMemo Hook 使控制具体子节点何时更新变得更容易,减少了对纯组件的需要。
最后,useReducer Hook 减少了对深层传递回调的需要,就如下面解释的那样。
如何避免向下传递回调?在大型的组件树中,我们推荐的替代方案是通过 context 用 useReducer 往下传一个 dispatch 函数:
const TodosDispatch = React.createContext(null); function TodosApp() { // 提示:`dispatch` 不会在重新渲染之间变化 const [todos, dispatch] = useReducer(todosReducer); return (); }
TodosApp 内部组件树里的任何子节点都可以使用 dispatch 函数来向上传递 actions
function DeepChild(props) { // 如果我们想要执行一个 action,我们可以从 context 中获取 dispatch。 const dispatch = useContext(TodosDispatch); function handleClick() { dispatch({ type: "add", text: "hello" }); } return ; }
总而言之,从维护的角度来这样看更加方便(不用不断转发回调),同时也避免了回调的问题。像这样向下传递 dispatch 是处理深度更新的推荐模式。
React 是如何把对 Hook 的调用和组件联系起来的?React 保持对当先渲染中的组件的追踪。多亏了 Hook 规范,我们得知 Hook 只会在 React 组件中被调用(或自定义 Hook —— 同样只会在 React 组件中被调用)。
每个组件内部都有一个「记忆单元格」列表。它们只不过是我们用来存储一些数据的 JavaScript 对象。当你用 useState() 调用一个 Hook 的时候,它会读取当前的单元格(或在首次渲染时将其初始化),然后把指针移动到下一个。这就是多个 useState() 调用会得到各自独立的本地 state 的原因。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/106505.html
摘要:已经被废除,具体缺陷可以参考二为了解决的缺陷,第二种解决方案是高阶组件简称。我们定义了父组件,存在自身的,并且将自身的通过的方式传递给了子组件。返回一个标识该的变量,以及更新该的方法。 为了实现分离业务逻辑代码,实现组件内部相关业务逻辑的复用,在React的迭代中针对类组件中的代码复用依次发布了Mixin、HOC、Render props等几个方案。此外,针对函数组件,在Reac...
摘要:已经被废除,具体缺陷可以参考二为了解决的缺陷,第二种解决方案是高阶组件简称。我们定义了父组件,存在自身的,并且将自身的通过的方式传递给了子组件。返回一个标识该的变量,以及更新该的方法。 为了实现分离业务逻辑代码,实现组件内部相关业务逻辑的复用,在React的迭代中针对类组件中的代码复用依次发布了Mixin、HOC、Render props等几个方案。此外,针对函数组件,在Reac...
摘要:已经被废除,具体缺陷可以参考二为了解决的缺陷,第二种解决方案是高阶组件简称。我们定义了父组件,存在自身的,并且将自身的通过的方式传递给了子组件。返回一个标识该的变量,以及更新该的方法。 为了实现分离业务逻辑代码,实现组件内部相关业务逻辑的复用,在React的迭代中针对类组件中的代码复用依次发布了Mixin、HOC、Render props等几个方案。此外,针对函数组件,在Reac...
摘要:与继承相比,装饰者是一种更轻便灵活的做法。它只是一种模式,这种模式是由自身的组合性质必然产生的。对比原生组件增强的项可操作所有传入的可操作组件的生命周期可操作组件的方法获取反向继承返回一个组件,继承原组件,在中调用原组件的。 导读 前端发展速度非常之快,页面和组件变得越来越复杂,如何更好的实现状态逻辑复用一直都是应用程序中重要的一部分,这直接关系着应用程序的质量以及维护的难易程度。 本...
摘要:系列系列简单模拟语法一系列合成事件与二系列算法实现分析三系列从到再到四系列与部分源码解析五系列从使用了解的各种使用方案六前言我们先不讲什么语法原理先根据效果强行模拟语法使用实现一个简易版的第一步我们先用类 React系列 React系列 --- 简单模拟语法(一)React系列 --- Jsx, 合成事件与Refs(二)React系列 --- virtualdom diff算法实现分析...
阅读 1213·2021-11-25 09:43
阅读 1968·2021-11-11 10:58
阅读 1186·2021-11-08 13:18
阅读 2657·2019-08-29 16:25
阅读 3508·2019-08-29 12:51
阅读 3305·2019-08-29 12:30
阅读 746·2019-08-26 13:24
阅读 3682·2019-08-26 10:38