React基础常见面试点总结
React 组件生命周期(Hooks 的表示)
import { useEffect, useState } from 'react'
function TestLifeCycleHook() {
const [state, setState] = useState('This is function Hook.')
// 模拟 componentDidMount 方法
useEffect(() => {
console.log('模拟---componentDidMount---')
}, []);
// 模拟 componentDidUpdate 方法
useEffect(() => {
console.log('模拟---componentDidUpdate---', state);
}, [state]);
// 模拟 componentWillUnmount 方法
useEffect(() => {
return () => {
console.log('模拟---componentWillUnmount---');
};
}, []);
const updateState = () => {
setState('This is function Hook. updateState')
}
return (
<>
<div>{state}</div>
<button onClick={updateState}>更新state</button>
</>
)
}
export default TestLifeCycleHook
React 组件的通讯方式
1.props 父传子
父组件
import { useState } from 'react';
import Child from './child'
export default function HelloWorld() {
const [name, setName] = useState('豆豆')
return (
<div>
// 把name传递给子组件
<Child name={name} />
</div>
)
}
子组件
import { useState } from 'react';
// 子组件利用对象解构赋值接收父组件传递的 name
export default function Child({name}) {
return (
<div>
// 子组件展示父组件传递的 name 值
{ name }
</div>
)
}
2.回调函数
父组件
import { useState } from 'react';
import Child from './child'
export default function HelloWorld() {
const [name, setName] = useState('豆豆')
// val的值通过子组件传递过来
const changeName = val => {
setName(val)
}
return (
<div>
{name}
// 把 changeName 传递给子组件
<Child changeName={changeName} />
</div>
)
}
子组件
import { useState } from 'react';
// 子组件接收 changeName 方法
export default function Child({changeName}) {
const handleClick = () => {
// 执行父组件传递过来的方式,并且给父组件传值
changeName('一只豆豆')
}
return (
<div>
<button onClick={handleClick}>click my</button>
</div>
)
}
3.跨组件通讯 useContext() 共享状态钩子
4.父组件调用子组件方法
在React中可以通过useImperativeHandle把子组件的方法暴露出去
父组件
import React, { useRef } from "react"
import Child from './child'
export default function Father() {
const myRef = useRef()
const handleClick = () => {
// 点击按钮时执行子组件的handleClick方法
myRef.current.handleClick()
}
return (
<>
<div>
父组件
<button onClick={handleClick}>click my</button>
<Child ref={myRef} />
</div>
</>
)
}
子组件
import React, { useImperativeHandle, forwardRef } from "react"
const Child = (props, ref) => {
useImperativeHandle(ref, () => ({
handleClick: () => {
console.log('七里香');
}
}))
return (
<>
<div>
子组件
</div>
</>
)
}
export default forwardRef(Child) // useImperativeHandle配合forwardRef一起使用
常见的几个Hooks,创建如何自定义一个 Hook 抽离公共逻辑
1.useState() 状态钩子
const [xxx, setXxx] = React.useState(initValue)
// setXxx的两种写法
setXxx(newValue): 参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值
setXxx(value => newValue): 参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值
// 具体使用
const [ count,setCount ] = useState(0)
addCount = ()=> {
let newCount = count;
setCount(newCount +=1)
}
2.useEffect() 副作用钩子
用来更好的执行副作用操作(用于模拟类组件中的生命周期钩子),
如异步请求,设置订阅 / 启动定时器,手动更改真实DOM等
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
参数、返回值说明:
- useEffect()接受两个参数,第一个参数是要进行的异步操作,第二个参数是一个数组,用来给出Effect的依赖项,只要这个数组发生变化,useEffect()就会执行。
- 当第二项省略不填时。useEffect()会在每次组件渲染时都会执行useEffect,只要更新就会执行。
- 第二项传 空数组[ ]时,只会在组件挂载后运行一次。
- useEffect()返回值可以是一个函数,在组件销毁的时候会被调用。清理这些副作用可以进行如取消订阅、清除定时器操作,类似于componentWillUnmount。
可以把 useEffect Hook 看做如下三个函数的组合 :
componentDidMount()、componentDidUpdate()、componentWillUnmount()
3.useContext() 共享状态钩子
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
- 创建一个context.js
import React from 'react' export const MyContext = React.createContext( // 在这里可以设置默认值 { name: '豆豆' }, )
- 在父组件引入 context.js
import { useState, } from 'react'; import Child from './child' import { MyContext } from './context' export default function HelloWorld() { const [name, setName] = useState('一只豆豆') return ( <div> // 包裹组件并且传递 name <MyContext.Provider value={name}> <Child /> </MyContext.Provider> </div> ) }
- 子组件引入孙子组件
// 只需要引入son组件 使用即可,不需要任何操作 import { useState } from 'react'; import Son from './son' export default function Child() { return ( <div> <Son /> </div> ) }
- 孙子组件使用传递过来的数据
// 后代组件读取数据 import { useContext} from 'react'; const {name} = useContext(XxxContext)
4.useReducer() Action钩子
提供了状态管理
const [state, dispatch] = useReducer(reducer, initialState)
// 数组第一项:当前的状态值
// 第二项:发送action的dispatch函数
import { useReducer } from "react";
const HookReducer = ()=> {
const reducer = (state,action)=> {
if (action.type === 'add') {
return {
...state,
count: state.count + 1
}
}else {
return state
}
}
const addCount = ()=> {
dispatch({
type: 'add'
})
}
const [state,dispatch ] = useReducer(reducer,{count: 0})
return (
<>
<p>{state.count}</p>
<button onClick={ addCount }>useReducer</button>
</>
)
}
export default HookReducer;
5.userRefef()
在函数组件中存储、查找组件内的标签或任意其它数据
const refContainer = useRef()
import{ useRef,useEffect} from "react";
const RefComponent = () => {
let inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
})
return (
<input type="text" ref={inputRef}/>
)
}
自定义hooks可以说成是一种约定而不是功能。当一个函数以use开头并且在函数内部调用其他hooks,那么这个函数就可以成为自定义hooks
import { useState,useEffect } from "react";
// 自定义Hooks
const usePerson = ({name}) => {
const [loading, setLoading] = useState(true)
const [person, setPerson] = useState({})
useEffect(() => {
setLoading(true)
setTimeout(()=> {
setLoading(false)
setPerson({name})
},2000)
},[name])
return [loading,person]
}
const AsyncPage = (name)=> {
const [loading,person] = usePerson(name)
return (
<>
{loading?<p>Loading...</p>:<p>{ person.name }</p>}
</>
)
}
const PersonPage = ()=> {
const [state,setState] = useState('')
const changeName = (name)=> {
setState(name)
}
return (
<>
<AsyncPage name={ state } />
<button onClick={ ()=> { changeName('郭靖')}}>郭靖</button>
<button onClick={ ()=> { changeName('黄蓉')}}>黄蓉</button>
</>
)
}
export default PersonPage;
state 不可变数据,immer.js
immer.js 其实是让 js 对于复杂对象(嵌套较深)的修改变得更加容易、可读
immer 的原理的基础是 ES6 中 Proxy
使用immer.js实现对一个深层嵌套数据的处理
import produce from 'immer';
// initialUser 是一个多层嵌套的变量
function User() {
const [user, setUser] = useState(initialState);
return (
<div>
<label>修改 user.address.geo.lat </label>
<input
value={user.address.geo.lat}
onChange={e => {
const newUser = produce(user, draft => {
draft.address.geo.lat = e.target.value;
});
setUser(newUser);
}}
/>
</div>
)
}
React 表单组件,受控组件
我们通过自己维护一个 state 来获取或更新 input 输入的值,以这种方式控制取值的 表单输入元素 就叫做 受控组件。
const [value,setValue] = useState('')
<input value={value} onChange={(e)=> setValue(e.target.value)} />
React-router 相关,如动态参数、路由懒加载
React Router中的组件主要分为三类:
1.路由器:例如 BrowserRouter 和 HashRouter
2.路由匹配器: 例如Route和Switch
3.导航:例如Link, NavLink, and Redirect
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
function App() {
<div>
<Switch>
{/* 如果当前URL是/ about,则呈现此路由而其余的被忽略 */}
<Route path="/about">
<About />
</Route>
{/* 请注意这两个路由的顺序。更具体的path =“ / contact /:id”在path =“ / contact”之前,因此查看单个联系人时,这个路线将被渲染 */}
<Route path="/contact/:id">
<Contact />
</Route>
<Route path="/contact">
<AllContacts />
</Route>
{/*
如果先前的路线都不提供任何东西,这条路线充当后备路线。
重要提示:路径为'/'的路线将始终匹配URL,这就是为什么我们把它放在最后。
*/}
<Route path="/">
<Home />
</Route>
</Switch>
</div>
}
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
)
// 导航
<Link to="/">Home</Link>
<NavLink to="/react" activeClassName="hurray">
React
</NavLink>
- 动态参数
1.params传参数
2.query传参优点:页面刷新,`参数不会丢失`; 缺点:传值太多不方便而且url会变的很长,参数在地址栏展示。 // 配置 <Router> <Switch> <Route path="/detail/:id" component={Detail} /> </Switch> </Router> // 使用 <Link to="/path/id">通配符</Link> onClick={() => props.history.push("/detail/" + item.id)} // 获取params参数 const id = props.match.params.id;
3.state传参优点:传参优雅,传递参数可传对象,参数不会在地址栏展示; 缺点:刷新地址栏,`参数丢失`。 // 配置 <Route path="/detail" component={Detail} /> // 使用 <Link to={{pathname:'/production',query:{xx_01:120,xx_02:'fruits'}}}> onClick={() => props.history.push({ pathname: "/detail", query: { id: item.id } }) } // 获取参数 const id = props.location.query?.id || '';
4.search传参优点:传参优雅,传递参数可传对象,参数不会在地址栏展示 缺点:使用 HashRouter 的话,刷新页面,参数会丢失 // 配置 <Route path="/detail" component={Detail} /> // 使用 <Link to={{ pathname:'/production', state:{ productionId:12, productionType:'fruits' } }}>跳转</Link> onClick={() => props.history.push({pathname: '/detail', state: {id: item.id}})} // 获取参数 const id = this.props.location.state
优点:页面刷新,参数不会丢失; 缺点:传值太多url也会变的很长。也需要自己手动去处理参数。 // 配置 <Route path="/detail" component={Detail} /> // 使用 <link to="web/departManange?tenantId=12121212">xxx</Link> onClick={() => props.history.push("/detail?id=" + item.id)} // 获取参数 const id = this.props.location.search
- 路由懒加载
const World = lazy(() => import('../World'))
CSS 的解决方案:CSS-Module css-in-js
1.CSS-Module
CSS Modules 指的是我们像 import js 一样去引入我们的 css 代码,代码中的每一个类名都是引入对象的一个属性, 编译时会将 css 类名 加上唯一 hash。
css module 需要 webpack 配置 css-loader 或者 scss-loader , module 为 true
// webpack 配置
{
loader: 'css-loader',
options: {
modules: true, // 开启模块化
localIdentName: '[path][name]-[local]-[hash:base64:5]'
}
}
使用CSS-Module前
// style.css
.home-wrapper{
background: #f00;
width: 100%;
height: 500px;
}
// index.js
import React from 'react';
import './style.css'
function Hello(props) {
return (
<div className='home-wrapper'></div>
);
}
export default Hello;
使用CSS-Module后
//style.module.css
.homeWrapper{
background: #f00;
width: 100%;
height: 500px;
}
// Hello.js
import React from 'react';
import styles from './style.module.css';
function Hello(props) {
return (
<div className={styles.homeWrapper}></div>
);
}
export default Hello;
2.CSS-In-JS: 是一种把 CSS 直接写在 JS 里的一种模式
styled-components 是针对 React 写的一套 css in js 框架, 在你使用 styled-components 进行样式定义的同时,你也就创建了一个 React 组件。css in js
const DivWrapper = styled.div`
width: '100%';
height: 300;
background-color: ${(props) => props.color};
`;
// 封装第三方组件库
const AntdButtonWrapper = styled(Button)`
color: 'red';
`;
// 通过属性动态定义样式
const MyButton = styled.button`
background: ${(props) => (props.primary ? 'palevioletred' : 'white')};
color: ${(props) => (props.primary ? 'white' : 'palevioletred')};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
// 样式复用
const TomatoButton = styled(MyButton)`
color: tomato;
border-color: tomato;
`;
// 创建关键帧
const rotate = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
// 创建动画组件
const Rotate = styled.div`
display: inline-block;
animation: ${rotate} 2s linear infinite;
padding: 2rem 1rem;
font-size: 1.2rem;
`;
Redux 的各种概念,单线数据流
1.Store
Store 是保存数据的地方,它是一个对象,一个应用只能有一个 Store
Rudex 提供了一个叫 createStore 的方法用来生成 Store
import { createStore } from 'redux';
// 接收一个函数作为参数,返回一个 Store 对象
const store = createStore(fn);
2.State
Store对象包含所有数据, State 代表某个时间点的 Store, 用来描述应用程序在特定时间点的状况;
获取当前时间点的 State: store.getState()
import { createStore } from 'redux';
const store = createStore(fn);
// 一个state对应一个view,只要state相同,view就相同
const state = store.getState();
3.Action
State 的变化会导致 View 变化,但是用户接触不到 State
通过Action通知Store, State 要发生变化了(描述对仓库做什么,是改变State的`唯一方式`)
const action = {
type: 'counter/add', // 表示Action的名称
payload: 'Add Operation' // 携带的数据
};
4.store.dispatch()
是 View 发出 Action 的唯一方法
import { createStore } from 'redux';
const store = createStore(fn);
const action = {
type: 'counter/add', // 表示Action的名称
payload: 'Add Operation' // 携带的数据
};
store.dispatch(action ) // 接收一个 Action 对象作为参数
5.Reducer
Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化
这种 State 的计算过程 就叫做 Reducer
Reducer 是一个纯函数,接收 Action 和当前 State 作为参数,决定如何更新状态,返回一个新的 State
注意:Reducer 必须是一个纯函数,也就是说函数返回的结果必须由参数 state 和 action 决定,而且不产生任何副作用也不能修改 state 和 action 对象
const reducer = (state = defaultState, action) => {
switch (action.type) {
case "counter/add":
return { ...state, counter: state.counter + 1 };
default:
return state;
}
}
// createStore 接收 Reducer 作为参数,生成一个新的 Store
// 以后每当 store.dispatch 发送过来一个新的 Action,就会自动调用Reducer,得到新的 State;
const store = createStore(reducer);
6.store.subscribe()
Store 允许使用 store.subscribe 方法设置监听函数,一旦 State 发生变化,就自动执行这个函数;
import { createStore } from 'redux';
const store = createStore(reducer);
const render = () => {
root.render(
<React.StrictMode>
<Counter
value={store.getState()}
onAdd={() => store.dispatch({type: 'counter/add'})}/>
</React.StrictMode>
);
}
store.subscribe(render);
setState 同步、异步、是否合并数据 —— 读代码
setState有时是同步,有时是异步
setState在合成事件和生命周期函数里是异步的,在原生事件和setTimeout里是同步的
import React from "react";
import "./styles.css";
export default class App extends React.Component{
state = {
count: 0
}
increment = () => {
console.log('increment setState前的count', this.state.count) // 更新前 输出:0
this.setState({
count: this.state.count + 1
});
// setState异步,数据还是更新 输出:0
console.log('increment setState后的count', this.state.count)
}
triple = () => {
// 数据已更新 输出:1
console.log('triple setState前的count', this.state.count)
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
// 批量更新即数据合并 只对最新的state值做一次更新流程
// 又因为setState异步执行 数据还未更新 输出:1
console.log('triple setState后的count', this.state.count)
}
reduce = () => {
setTimeout(() => {
// 数据已批量更新 输出:2
console.log('reduce setState前的count', this.state.count)
this.setState({
count: this.state.count - 1
});
// setTimeout里面的setState是同步的 输出:1
console.log('reduce setState后的count', this.state.count)
},0);
}
render(){
return <div>
<button onClick={this.increment}>点我增加</button>
<button onClick={this.triple}>点我增加三倍</button>
<button onClick={this.reduce}>点我减少</button>
</div>
}
}
React 性能优化相关的
1.类组件优化手段
使用纯组件
PureComponent
作为基类,用于避免不必要的渲染。
使用shouldComponentUpdate
生命周期函数来自定义渲染逻辑。
2.方法组件中的优化手段
使用
React.memo
高阶函数包装组件
使用React.useMemo
精细化的管控,useMemo 控制的则是是否需要重复执行某一段逻辑
,而React.memo 控制是否需要重渲染一个组件
使用useCallBack
,只有在依赖项发生变化时才会重新计算,否则会直接返回上一次计算的结果
3.其他方式
- 在列表需要频繁变动时,使用唯一 id 作为 key,而不是数组下标。
- 使用 Suspense 和 lazy 进行路由懒加载