react 题目
react 引入css的几种方式
遵循规则 -- 不能污染其他组件 -- 支持动态css -- 支持css所有特性 -- 编写起来方便
- 1 变量式声明 const div1={width:'300px'} 内联样式
- 2 组件中 import 引入css文件
- 3 css module.css 文件
react 中常见的钩子
useState
useMemo 计算属性
useCallback 计算函数
useContext 获取上下文对象,传值
const Ctx = createContext("admin");
const currCtx = useContext(Ctx);
<Ctx.Provider value={'value'}>
<Child/>
</Ctx.Provider>
const Child = () => {
return
<Fragment>
<Ctx.Consumer>
{value => value.toString()}
</Ctx.Consumer>
</Fragment>useRef 通过ref获取实例
const r = useRef(null);
const render = () => {
return <div ref={r}>this is ref</div>
}
r.current.innerHTML = 'this is set ref'useEffect 生命周期钩子结合 初次渲染 更新 销毁
useLayoutEffect 重绘前触发生命周期钩子 阻塞更新,解决界面闪动
useReducer dispatch修改
const [state, dispatch] = useReducer((state, action) => {
if (action.type === '1') {
return {count: state.count + 1}
}
return {count: state.count + 2}
}, {count: 1})forwardRef 函数组件不能ref访问 useImperativeHandle 用来给父组件暴露 当前组件的属性
const component = React.forwardRef((props, ref) => {
function fn() {
console.log("子组件的fn")
}
useImperativeHandle(ref, () => ({
fn
}), [fn]);
return <div>component</div>
})useHistory 路由前进后退
useLocation 路由参数获取
生命周期
- 初始化
- 更新
- 销毁
1、getDefaultProps() 设置默认的props,也可以用dufaultProps设置组件的默认属性.
2、getInitialState() 在使用es6的class语法时是没有这个钩子函数的,可以直接在constructor中定义this.state。此时可以访问this.props
3、componentWillMount() 组件初始化时只调用,以后组件更新不调用,整个生命周期只调用一次,此时可以修改state。 在渲染前调用,在客户端也在服务端。
4、 render() react最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行。此时就不能更改state了。
5、componentDidMount() 组件渲染之后调用,只调用一次。
在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。
如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作( 防止异步操作阻塞UI)。
- 6、componentWillReceiveProps(nextProps)
组件初始化时不调用,组件接受新的props时调用。
使用componentWillReceiveProps的时候,不要去向上分发,调用父组件的相关setState方法,否则会成为死循环
在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。
- 7、shouldComponentUpdate(nextProps, nextState)
react性能优化非常重要的一环。组件接受新的state或者props时调用,我们可以设置在此对比前后两个props和state是否相同,
如果相同则返回false阻止更新,因为相同的属性状态一定会生成相同的dom树,这样就不需要创造新的dom树和旧的dom树进行diff算法对比,
节省大量性能,尤其是在dom结构复杂的时候
返回一个布尔值。在组件接收到新的props或者state时被调用。
在初始化时或者使用forceUpdate时不被调用。
可以在你确认不需要更新组件时使用。
- 8、componentWillUpdata(nextProps, nextState)
组件初始化时不调用,只有在组件将要更新时才调用,此时可以修改state
- 9、render()
组件渲染
- 10、componentDidUpdate()
组件初始化时不调用,组件更新完成后调用,此时可以获取dom节点。
卸载
- 11、componentWillUnmount()
组件将要卸载时调用,一些事件监听和定时器需要在此时清除。
生命周期 调用次数 能否使用 setSate() getDefaultProps 1(全局调用一次) 否 getInitialState 1 否 componentWillMount 1 是 render >=1 否 componentDidMount 1 是 componentWillReceiveProps >=0 是 shouldComponentUpdate >=0 否 componentWillUpdate >=0 否 componentDidUpdate >=0 是 componentWillUnmount 1 否
事件的传播和捕获执行顺序
事件捕获阶段 -> 事件执行阶段 -> 事件冒泡阶段。
- 事件捕获阶段的处理程序
- 事件的目标元素上定义的处理程序
- 事件冒泡阶段的处理程序 通过将 options 参数设置为 true,可以在事件捕获阶段执行事件处理程序。这样做可以在事件到达目标元素之前捕获并处理事件,从而实现更精细的事件控制。 需要注意的是,并非所有的事件都支持事件捕获阶段,比如 focus 和 blur 事件就不支持。此外,事件捕获和事件冒泡的默认顺序是不可逆的,可以通过设置 options 参数为 true 来改变它们的顺序,但是不能完全颠倒它们的顺序。
react 合成事件
document 捕获 > body 捕获 > react 组件根节点 捕获(合成) > root 捕获 > inner 节点 捕获(合成)> inner 节点 冒泡 (原生)> inner 节点 冒泡 (合成) root 冒泡 > body 冒泡 > document 冒泡

setState 同步/异步
这是 React 此次版本中最大的破坏性更新,并且无法向下兼容。
React 中的批处理简单来说就是将多个状态更新合并为一次重新渲染,以获得更好的性能,在 React 18 之前,React 只能在组件的生命周期函数或者合成事件函数中进行批处理。默认情况下,Promise、setTimeout 以及原生事件中是不会对其进行批处理的。如果需要保持批处理,则可以用 unstable_batchedUpdates 来实现,但它不是一个正式的 API。
React 18 之前:
function handleClick() {
setCount(1);
setFlag(true);
// 批处理:会合并为一次 render
}
async function handleClick() {
await setCount(2);
setFlag(false);
// 同步模式:会执行两次 render
// 并且在 setCount 后,在 setFlag 之前能通过 Ref 获取到最新的 count 值
}总结
react18 之前
- 异步代码块中使用setState 可以同步获取修改后的值
- 非异步代码块中获取setState 获取不到修改后的值
- react 18 将所有的setState 都改为批量更新 也就是异步的
setState对象式和函数式
const [count1, setCount1] = useState(0)
const [count2, setCount2] = useState(0)
function app() {
for (var i = 0; i < 10; i++) {
setTimeout(() => {
setCount1(count1 + 1)
console.log(count1, 'count1');// 输出十次0
})
}
for (var i = 0; i < 10; i++) {
setTimeout(() => {
setCount2((old) => old + 1)
console.log(count1, 'count2');// 输出十次0
})
}
return {
render() {
return <div>
{count1}
{/* count1 = 1 */}
{count2}
{/* count2 = 10 */}
</div>
}
}
}结论: 函数式setState使用场景: 依赖于上一次更新值,避免异步更新导致的问题
函数式组件和类组件区别
类组件
import React, {Component} from 'react'
class App extends Component {
state = {
number: 0
}
handleClick = () => {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
this.setState({number: this.state.number + 1})
console.log(this.state.number);// 1 2 3 4 5
}, 1000)
}
}
render() {
return (
<div>
当前的number:{this.state.number}
{/* 5 */}
<button onClick={this.handleClick}>点击我</button>
</div>
)
}
}
export default App函数组件
import React, {useState} from 'react';
const App = () => {
const [num, setNumber] = useState(0)
const handleClick1 = () => {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
setNumber(num + 1)
console.log(num) // useState 状态每次更新都重新执行函数 0
}, 1000)
}
}
return (
<div>
当前的num值:{num}
{/* 1 */}
<br/>
<button onClick={handleClick1}>改变num</button>
</div>
);
};
export default App;结论
类class组件中,通过一个实例化的class,去维护组件中的各种状态。点击按钮,循环五次setTimeout,同时由于setState没有在react正常的函数里执行上下文,而是位于异步的setTimeout里面,所以批量更新的条件被破坏,可以同步拿到每次setState之后的值 (关于React批量更新可查看: React学习笔记——this.setState的基础使用和不同传参方法详解) 函数Function组件中,没有一个状态去保存这些信息,它只是一个函数,每一次函数上下文执行,所有变量,常量都重新声明,执行完毕,再被垃圾机制回收。所以如上,无论setTimeout执行多少次,都是在当前函数上下文执行,此时num = 0不会变,之后setNumber执行,函数组件重新执行之后,num才变化。
所以, 对于class组件,我们只需要实例化一次,实例中保存了组件的state等状态。对于每一次更新只需要调用render方法就可以。 但是在function组件中,我们每一次更新都是一次新的函数执行,为了保存一些状态,执行一些副作用钩子,react-hooks应运而生,去帮助记录组件的状态,处理一些额外的副作用。
flushSync 用于退出批量更新改为同步更新
function fn() {
flushSync(() => {
setState(0)
})
setState(0)
}