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传参数
      优点:页面刷新,`参数不会丢失`;
      缺点:传值太多不方便而且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;
    
    2.query传参
      优点:传参优雅,传递参数可传对象,参数不会在地址栏展示; 
      缺点:刷新地址栏,`参数丢失`// 配置
      <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 || '';
    
    3.state传参
      优点:传参优雅,传递参数可传对象,参数不会在地址栏展示 
      缺点:使用 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
    
    4.search传参
      优点:页面刷新,参数不会丢失; 
      缺点:传值太多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 进行路由懒加载
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值