先来看个简单的例子
// Parent.jsx
import react, { useState } from 'react';
import Child from '../Child';
function Parent() {
const [parentCount, setParentCount] = useState(0);
console.log('父組件重新渲染--------------');
return (
<div style={{ background: 'lightseagreen' }}>
<Child />
<button type="button" onClick={() => { setParentCount(parentCount + 1); }}>父组件 +1</button>
</div>
);
}
export default Parent;
// Child.jsx
import React from 'react';
function Child() {
console.log('------------子組件重新渲染');
return (
<div style={{ background: 'pink', margin: '50px 0' }}>
<button type="button">子組件</button>
</div>
);
}
export default Child;
当我们点击父组件按钮时,父组件的状态parentCount会被更新,导致父组件重新渲染,子组件也会重新渲染;而此时我们的子组件和父组件之间并没有依赖关系,因此这种重复渲染是可以优化掉的,可以使用React.memo 包裹子组件
// Child.jsx
import React from 'react';
// ...other code
export default React.memo(Child);
用于减少子组件的重新渲染
React.memo是一个高阶组件(参数为组件,返回值为新组件的函数即为高阶组件)
对外部而言,React.memo会检查props的变更,仅当传入的props发生变化时组件才会重新渲染,这时我们再点击父组件按钮,子组件就不会重新渲染了
React.memo对复杂对象只会做浅层对比,可以通过传入第二个参数来控制对比过程
第二个参数为一个接收重新渲染前后props的函数
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);
用于减少每次组件重新渲染时重复进行复杂的计算,参数为一个函数和可选的依赖项数组,返回传入函数的调用结果
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo作用类似于vue 的 computed (计算属性),不同之处在于需要手动传入依赖项,当依赖项变更时会重新调用传入的函数,返回计算值
传入依赖项为空数组时则直接返回上次的计算结果
不传入依赖项时,每次组件刷新都会重新计算,应该在代码能正常运行的情况下将其作为一种优化策略
修改下我们的例子,注意这里用React.memo 包裹了子组件,保证测试时子组件重新渲染只受传入的props变化的影响
// Parent.jsx
import React, { useState, useMemo } from 'react';
import Child from '../Child';
function Parent() {
const [parentCount, setParentCount] = useState(0);
const [otherCount, setOtherCount] = useState(0);
console.log('父組件重新渲染--------------');
// 一个复杂的计算
const computedFn = (a, b) => {
console.log('----重新执行了计算----');
return a + b;
};
const computedValue = useMemo(() => {
return computedFn(parentCount, 1);
}, [parentCount]);
return (
<div style={{ background: 'lightseagreen' }}>
<Child parentCount={parentCount} computedValue={computedValue} />
<button type="button" onClick={() => { setParentCount(parentCount + 1); }}>父组件 +1</button>
<button type="button" onClick={() => { setOtherCount(otherCount + 1); }}>父组件 otherCount+1</button>
</div>
);
}
点击第一个按钮,依赖项变更,输出重新执行了计算,点击第二个按钮,因为更改的不是计算值的依赖项,因此不会重新计算,子组件也不会重新渲染
用于需要传递给子组件的函数,减少子组件的重复渲染,参数为一个函数和可选的依赖项数组,返回出入函数的记忆版本
// Parent.jsx
import React, { useState } from 'react';
import Child from '../Child';
function Parent() {
const [parentCount, setParentCount] = useState(0);
const [otherCount, setOtherCount] = useState(0);
console.log('父組件重新渲染--------------');
const computedFn = () => {
return parentCount + 1;
};
return (
<div style={{ background: 'lightseagreen' }}>
<Child parentCount={parentCount} computedFn={computedFn} />
<button type="button" onClick={() => { setParentCount(parentCount + 1); }}>父组件 +1</button>
<button type="button" onClick={() => { setOtherCount(otherCount + 1); }}>父组件 otherCount+1</button>
</div>
);
}
export default Parent;
// Child.jsx
import React from 'react';
function Child(props) {
const { computedValue, computedFn } = props;
console.log('------------子組件重新渲染');
return (
<div style={{ background: 'pink', margin: '50px 0' }}>
<div>
父组件传入的计算结果:
{computedValue}
</div>
<button type="button" onClick={computedFn}>子組件</button>
</div>
);
}
export default React.memo(Child);
当点击第二个按钮时,子组件也会重新渲染
给computedFn 加上useCallBack
// Parent.jsx
import React, { useState, useCallback } from 'react';
// ...other code
const computedFn = useCallback(() => {
console.log(parentCount);
return parentCount + 1;
}, [parentCount]) ;
// ...other code
export default Parent;
这时再点击父组件第二个按钮子组件,子组件不会重新渲染,因为useCallback 的依赖项没变更,返回的是上一次渲染的函数,因此传入子组件的props没变,组件不会重新渲染
需要注意的是,被useCallback保存的函数内部作用域也不会变更,因此,当依赖项数组为空的时候,传入useCallback的函数的内部通过闭包取的组件内的变量值终不变
import React, { useState, useCallback } from 'react';
import Child from '../Child';
let a = 0;
function Parent() {
const [parentCount, setParentCount] = useState(0);
const [otherCount, setOtherCount] = useState(0);
console.log('父組件重新渲染--------------');
const computedFn = useCallback(() => {
// 依赖项为空,这里的打印值始终不变;
// 因为组件state变化时会重新渲染整个组件,而这里parentCount取的始终是第一次渲染版本的值
console.log(parentCount);
// 这里的打印值会实时更新,因为变量直接定义在组件外部,不受组件重新渲染影响
console.log(a);
return parentCount + 1;
}, []) ;
return (
<div style={{ background: 'lightseagreen' }}>
<Child parentCount={parentCount} computedFn={computedFn} />
<button type="button" onClick={() => { setParentCount(parentCount + 1); a += 1; }}>父组件 +1</button>
<button type="button" onClick={() => { setOtherCount(otherCount + 1); }}>父组件 otherCount+1</button>
</div>
);
}
export default Parent;
因为useCallback目的是减少子组件重渲染,因此需要搭配子组件的shouldComponentUpdate或 React.memo 一起使用才有优化意义
以上是依赖项变更不频繁的情况,当依赖项变更频繁时,useCallback的记忆效果就不好,可以使用ref 作为依赖项解决
function Form() {
const [text, updateText] = useState('');
const textRef = useRef();
useEffect(() => {
textRef.current = text; // 把它写入 ref
});
const handleSubmit = useCallback(() => {
// ref 对象在组件的整个生命周期内保持不变
// 从 ref 读取它,current的变更不会引起组件的重新渲染,而函数内部又能拿到正确的值
const currentText = textRef.current;
alert(currentText);
}, [textRef]);
return (
<>
<input value={text} onChange={e => updateText(e.target.value)} />
<ExpensiveTree onSubmit={handleSubmit} />
</>
);
}
看看官方介绍
const refContainer = useRef(initialValue);
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变
可以理解为:用useRef创建的对象有个current 属性,这个属性就像个盒子,啥都能存,包括dom节点;返回的 ref 对象在组件的整个生命周期内保持不变,即存在current的值不受组件重新渲染影响,始终保持着一开始的引用;同时该属性的变更也不会触发组件的重新渲染;这个属性的初始值为useRef的参数
看看官方例子
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
当把useRef 创建的对象传给DOM 元素的ref属性时,react会把当前DOM元素的引用存入current属性,这样就可以通过ref对象直接操作DOM元素了。
如今的 Web 前端已被 React、Vue 和 Angular 三分天下,尽管现在的 jQuery 已不再那么流行,但 jQuery 的设计思想还是非常值得致敬和学习的,特别是 jQuery 的插件化。
受控组件与非受控组件在官网与国内网上的资料都不多,有些人觉得它可有可不有,也不在意。这恰恰显示React的威力,满足不同规模大小的工程需求。
一般在传统模式下,我们构建前端项目很简单。就是下载各种js文件,如JQuery、Echart等,直接放置在html静态文件。Webpack则是JavaScript中比较知名的打包工具。这两个构建工具构成了React应用快速搭建的基础。
Gatsby能快速的使用 React 生态系统来生成静态网站,可以结合React Component、Markdown 和服务端渲染来完成静态网站生成让他更强大。
React推出后,出于不同的原因先后出现三种定义react组件的方式,殊途同归;具体的三种方式:函数式定义的无状态组件、es5原生方式React.createClass定义的组件、es6形式的extends React.Component定义的组件
React主要思想是通过构建可复用组件来构建用户界面,每个组件都有自己的生命周期,它规定了组件的状态和方法需要在哪个阶段改变和执行。所谓组件就是有限状态机,,表示有限个状态以及在这些状态之间的转移和动作行为的模型。
React 相关的优化:使用 babel-react-optimize 对 React 代码进行优化,检查没有使用的库,去除 import 引用,按需打包所用的类库,比如 lodash 、echarts 等.Webpack 构建打包存在的问题两个方面:构建速度慢,打包后的文件体积过大
这篇文章主要介绍React Router定义路由之后如何传值,有关React和React Router 。react router中页面传值的三种方法:props.params、query、state
react 高阶组件简单的理解是:一个包装了另一个基础组件的组件。高阶组件的两种形式:属性代理(Props Proxy)、反向继承 (Inheritance Inversion)
React 支持一种非常特殊的属性 Ref ,你可以用来绑定到 render() 输出的任何组件上。这个特殊的属性允许你引用 render() 返回的相应的支撑实例( backing instance )。这样就可以确保在任何时间总是拿到正确的实例
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!