关于react.js:社招前端常考react面试题总结

1次阅读

共计 12471 个字符,预计需要花费 32 分钟才能阅读完成。

react 强制刷新

component.forceUpdate() 一个不罕用的生命周期办法, 它的作用就是强制刷新

官网解释如下

默认状况下,当组件的 state 或 props 发生变化时,组件将从新渲染。如果 render() 办法依赖于其余数据,则能够调用 forceUpdate() 强制让组件从新渲染。

调用 forceUpdate() 将以致组件调用 render() 办法,此操作会跳过该组件的 shouldComponentUpdate()。但其子组件会触发失常的生命周期办法,包含 shouldComponentUpdate() 办法。如果标记发生变化,React 仍将只更新 DOM。

通常你应该防止应用 forceUpdate(),尽量在 render() 中应用 this.props 和 this.state。

shouldComponentUpdate 在初始化 和 forceUpdate 不会执行

react-router 里的 <Link> 标签和 <a> 标签有什么区别

比照 <a>,Link 组件防止了不必要的重渲染

React 组件中怎么做事件代理?它的原理是什么?

React 基于 Virtual DOM 实现了一个 SyntheticEvent 层(合成事件层),定义的事件处理器会接管到一个合成事件对象的实例,它合乎 W3C 规范,且与原生的浏览器事件领有同样的接口,反对冒泡机制,所有的事件都主动绑定在最外层上。

在 React 底层,次要对合成事件做了两件事:

  • 事件委派: React 会把所有的事件绑定到构造的最外层,应用对立的事件监听器,这个事件监听器上维持了一个映射来保留所有组件外部事件监听和处理函数。
  • 主动绑定: React 组件中,每个办法的上下文都会指向该组件的实例,即主动绑定 this 为以后组件。

React-Router 怎么设置重定向?

应用 <Redirect> 组件实现路由的重定向:

<Switch>
  <Redirect from='/users/:id' to='/users/profile/:id'/>
  <Route path='/users/profile/:id' component={Profile}/>
</Switch>

当申请 /users/:id 被重定向去 '/users/profile/:id'

  • 属性 from: string:须要匹配的将要被重定向门路。
  • 属性 to: string:重定向的 URL 字符串
  • 属性 to: object:重定向的 location 对象
  • 属性 push: bool:若为真,重定向操作将会把新地址退出到拜访历史记录外面,并且无奈回退到后面的页面。

什么是上下文 Context

Context 通过组件树提供了一个传递数据的办法,从而防止了在每一个层级手动的传递 props 属性。

  • 用法:在父组件上定义 getChildContext 办法,返回一个对象,而后它的子组件就能够通过 this.context 属性来获取
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
class Header extends Component{render() {
        return (
            <div>
                <Title/>
            </div>
        )
    }
}
class Title extends Component{
    static contextTypes={color:PropTypes.string}
    render() {
        return (<div style={{color:this.context.color}}>
                Title
            </div>
        )
    }
}
class Main extends Component{render() {
        return (
            <div>
                <Content>
                </Content>
            </div>
        )
    }
}
class Content extends Component{
    static contextTypes={
        color: PropTypes.string,
        changeColor:PropTypes.func
    }
    render() {
        return (<div style={{color:this.context.color}}>
                Content
                <button onClick={()=>this.context.changeColor('green')}> 绿色 </button>
                <button onClick={()=>this.context.changeColor('orange')}> 橙色 </button>
            </div>
        )
    }
}
class Page extends Component{constructor() {super();
        this.state={color:'red'};
    }
    static childContextTypes={
        color: PropTypes.string,
        changeColor:PropTypes.func
    }
    getChildContext() {
        return {
            color: this.state.color,
            changeColor:(color)=>{this.setState({color})
            }
        }
    }
    render() {
        return (
            <div>
                <Header/>
                <Main/>
            </div>
        )
    }
}
ReactDOM.render(<Page/>,document.querySelector('#root'));

在结构函数调用 super 并将 props 作为参数传入的作用

在调用 super() 办法之前,子类构造函数无奈应用 this 援用,ES6 子类也是如此。
将 props 参数传递给 super() 调用的次要起因是在子构造函数中可能通过 this.props 来获取传入的 props

传递了 props

class MyComponent extends React.Component {constructor(props) {super(props);
    console.log(this.props); // {name: 'sudheer',age: 30}
  }
}

没传递 props

class MyComponent extends React.Component {constructor(props) {super();
    console.log(this.props); // undefined
    // 然而 Props 参数依然可用
    console.log(props); // Prints {name: 'sudheer',age: 30}
  }
  render() {
    // 构造函数内部不受影响
    console.log(this.props); // {name: 'sudheer',age: 30}
  }
}

参考 前端进阶面试题具体解答

React Hooks 在平时开发中须要留神的问题和起因

(1)不要在循环,条件或嵌套函数中调用 Hook,必须始终在 React 函数的顶层应用 Hook

这是因为 React 须要利用调用程序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用 Hook,就容易导致调用程序的不一致性,从而产生难以预料到的结果。

(2)应用 useState 时候,应用 push,pop,splice 等间接更改数组对象的坑

应用 push 间接更改数组无奈获取到新值,应该采纳析构形式,然而在 class 外面不会有这个问题。代码示例:

function Indicatorfilter() {let [num,setNums] = useState([0,1,2,3])
  const test = () => {
    // 这里坑是间接采纳 push 去更新 num
    // setNums(num)是无奈更新 num 的
    // 必须应用 num = [...num ,1]
    num.push(1)
    // num = [...num ,1]
    setNums(num)
  }
return (
    <div className='filter'>
      <div onClick={test}> 测试 </div>
        <div>
          {num.map((item,index) => (<div key={index}>{item}</div>
          ))}      </div>
    </div>
  )
}

class Indicatorfilter extends React.Component<any,any>{constructor(props:any){super(props)
      this.state = {nums:[1,2,3]
      }
      this.test = this.test.bind(this)
  }

  test(){
      // class 采纳同样的形式是没有问题的
      this.state.nums.push(1)
      this.setState({nums: this.state.nums})
  }

  render(){let {nums} = this.state
      return(
          <div>
              <div onClick={this.test}> 测试 </div>
                  <div>
                      {nums.map((item:any,index:number) => (<div key={index}>{item}</div>
                      ))}                  </div>
          </div>

      )
  }
}

(3)useState 设置状态的时候,只有第一次失效,前期须要更新状态,必须通过 useEffect

TableDeail 是一个公共组件,在调用它的父组件外面,咱们通过 set 扭转 columns 的值,认为传递给 TableDeail 的 columns 是最新的值,所以 tabColumn 每次也是最新的值,然而理论 tabColumn 是最开始的值,不会随着 columns 的更新而更新:

const TableDeail = ({columns,}:TableData) => {const [tabColumn, setTabColumn] = useState(columns) 
}

// 正确的做法是通过 useEffect 扭转这个值
const TableDeail = ({columns,}:TableData) => {const [tabColumn, setTabColumn] = useState(columns) 
    useEffect(() =>{setTabColumn(columns)},[columns])
}

(4)善用 useCallback

父组件传递给子组件事件句柄时,如果咱们没有任何参数变动可能会选用 useMemo。然而每一次父组件渲染子组件即便没变动也会跟着渲染一次。

(5)不要滥用 useContext

能够应用基于 useContext 封装的状态管理工具。

如何将两个或多个组件嵌入到一个组件中?

能够通过以下形式将组件嵌入到一个组件中:

class MyComponent extends React.Component{render(){
        return(          
            <div>
                <h1>Hello</h1>
                <Header/>
            </div>
        );
    }
}
class Header extends React.Component{render(){
        return
            <h1>Header Component</h1>   
   };
}
ReactDOM.render(<MyComponent/>, document.getElementById('content')
);

hooks 父子传值

父传子
在父组件中用 useState 申明数据
 const [data, setData] = useState(false)

把数据传递给子组件
<Child data={data} />

子组件接管
export default function (props) {const { data} = props
    console.log(data)
}
子传父
子传父能够通过事件办法传值,和父传子有点相似。在父组件中用 useState 申明数据
 const [data, setData] = useState(false)

把更新数据的函数传递给子组件
<Child setData={setData} />

子组件中触发函数更新数据,就会间接传递给父组件
export default function (props) {const { setData} = props
    setData(true)
}
如果存在多个层级的数据传递,也可按照此办法顺次传递

// 多层级用 useContext
const User = () => {
 // 间接获取,不必回调
 const {user, setUser} = useContext(UserContext);
 return <Avatar user={user} setUser={setUser} />;
};

React 中 constructor 和 getInitialState 的区别?

两者都是用来初始化 state 的。前者是 ES6 中的语法,后者是 ES5 中的语法,新版本的 React 中曾经废除了该办法。

getInitialState 是 ES5 中的办法,如果应用 createClass 办法创立一个 Component 组件,能够主动调用它的 getInitialState 办法来获取初始化的 State 对象,

var APP = React.creatClass ({getInitialState() {
    return { 
        userName: 'hi',
        userId: 0
     };
 }
})

React 在 ES6 的实现中去掉了 getInitialState 这个 hook 函数,规定 state 在 constructor 中实现,如下:

Class App extends React.Component{constructor(props){super(props);
      this.state={};}
  }

React 的事件和一般的 HTML 事件有什么不同?

区别:

  • 对于事件名称命名形式,原生事件为全小写,react 事件采纳小驼峰;
  • 对于事件函数解决语法,原生事件为字符串,react 事件为函数;
  • react 事件不能采纳 return false 的形式来阻止浏览器的默认行为,而必须要地明确地调用 preventDefault() 来阻止默认行为。

合成事件是 react 模仿原生 DOM 事件所有能力的一个事件对象,其长处如下:

  • 兼容所有浏览器,更好的跨平台;
  • 将事件对立寄存在一个数组,防止频繁的新增与删除(垃圾回收)。
  • 不便 react 对立治理和事务机制。

事件的执行程序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件阻止冒泡,可能会导致合成事件不执行,因为须要冒泡到 document 上合成事件才会执行。

React 中的高阶组件使用了什么设计模式?

应用了装璜模式,高阶组件的使用:

function withWindowWidth(BaseComponent) {
  class DerivedClass extends React.Component {
    state = {windowWidth: window.innerWidth,}
    onResize = () => {
      this.setState({windowWidth: window.innerWidth,})
    }
    componentDidMount() {window.addEventListener('resize', this.onResize)
    }
    componentWillUnmount() {window.removeEventListener('resize', this.onResize);
    }
    render() {return <BaseComponent {...this.props} {...this.state}/>
    }
  }
  return DerivedClass;
}
const MyComponent = (props) => {return <div>Window width is: {props.windowWidth}</div>
};
export default withWindowWidth(MyComponent);

装璜模式的特点是不须要扭转 被装璜对象 自身,而只是在里面套一个外壳接口。JavaScript 目前曾经有了原生装璜器的提案,其用法如下:

@testable
   class MyTestableClass {}

什么是高阶组件

高阶组件不是组件,是 加强函数,能够输出一个元组件,返回出一个新的加强组件

  • 属性代理 (Props Proxy) 在我看来属性代理就是提取公共的数据和办法到父组件,子组件只负责渲染数据,相当于设计模式里的模板模式,这样组件的重用性就更高了
function proxyHoc(WrappedComponent) {
    return class extends React.Component {render() {
            const newProps = {count: 1}
            return <WrappedComponent {...this.props} {...newProps} />
        }
    }
}
  • 反向继承
const MyContainer = (WrappedComponent)=>{
    return class extends WrappedComponent {render(){return super.render();
        }
    }
}

高阶组件的利用场景

权限管制

利用高阶组件的 条件渲染 个性能够对页面进行权限管制,权限管制个别分为两个维度: 页面级别 页面元素级别

// HOC.js    
function withAdminAuth(WrappedComponent) {    
    return class extends React.Component {    
        state = {isAdmin: false,}    
        async componentWillMount() {const currentRole = await getCurrentUserRole();    
            this.setState({isAdmin: currentRole === 'Admin',});    
        }    
        render() {if (this.state.isAdmin) {return <WrappedComponent {...this.props} />;    
            } else {return (<div> 您没有权限查看该页面,请分割管理员!</div>);    
            }    
        }    
    };    
}

// 应用
// pages/page-a.js    
class PageA extends React.Component {constructor(props) {super(props);    
        // something here...    
    }    
    componentWillMount() {// fetching data}    
    render() {// render page with data}    
}    
export default withAdminAuth(PageA);    

可能你曾经发现了,高阶组件其实就是装璜器模式在 React 中的实现:通过给函数传入一个组件(函数或类)后在函数外部对该组件(函数或类)进行性能的加强(不批改传入参数的前提下),最初返回这个组件(函数或类),即容许向一个现有的组件增加新的性能,同时又不去批改该组件,属于 包装模式(Wrapper Pattern) 的一种。

什么是装璜者模式:在不扭转对象本身的前提下在程序运行期间动静的给对象增加一些额定的属性或行为

能够进步代码的复用性和灵活性

再对高阶组件进行一个小小的总结:

  • 高阶组件 不是组件 一个把某个组件转换成另一个组件的 函数
  • 高阶组件的次要作用是 代码复用
  • 高阶组件是 装璜器模式在 React 中的实现

封装组件的准则

封装准则

1、繁多准则:负责繁多的页面渲染

2、多重职责:负责多重职责,获取数据,复用逻辑,页面渲染等

3、明确承受参数:必选,非必选,参数尽量设置以_结尾,防止变量反复

4、可扩大:需要变动可能及时调整,不影响之前代码

5、代码逻辑清晰

6、封装的组件必须具备高性能,低耦合的个性

7、组件具备繁多职责:封装业务组件或者根底组件,如果不能给这个组件起一个有意义的名字,证实这个组件承当的职责可能不够繁多,须要持续抽组件,直到它能够是一个独立的组件即可

Redux 中异步的申请怎么解决

能够在 componentDidmount 中间接进⾏申请⽆须借助 redux。然而在⼀定规模的项⽬中, 上述⽅法很难进⾏异步流的治理, 通常状况下咱们会借助 redux 的异步中间件进⾏异步解决。redux 异步流中间件其实有很多,当下支流的异步中间件有两种 redux-thunk、redux-saga。

(1)应用 react-thunk 中间件

redux-thunk长处:

  • 体积⼩: redux-thunk 的实现⽅式很简略, 只有不到 20 ⾏代码
  • 使⽤简略: redux-thunk 没有引⼊像 redux-saga 或者 redux-observable 额定的范式, 上⼿简略

redux-thunk缺点:

  • 样板代码过多: 与 redux 自身⼀样, 通常⼀个申请须要⼤量的代码, ⽽且很多都是反复性质的
  • 耦合重大: 异步操作与 redux 的 action 偶合在⼀起, 不⽅便治理
  • 性能孱弱: 有⼀些理论开发中常⽤的性能须要⾃⼰进⾏封装

应用步骤:

  • 配置中间件,在 store 的创立中配置
import {createStore, applyMiddleware, compose} from 'redux';
import reducer from './reducer';
import thunk from 'redux-thunk'

// 设置调试工具
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
// 设置中间件
const enhancer = composeEnhancers(applyMiddleware(thunk)
);

const store = createStore(reducer, enhancer);

export default store;
  • 增加一个返回函数的 actionCreator,将异步申请逻辑放在外面
/**  发送 get 申请,并生成相应 action,更新 store 的函数  @param url {string} 申请地址  @param func {function} 真正须要生成的 action 对应的 actionCreator  @return {function} */
// dispatch 为主动接管的 store.dispatch 函数 
export const getHttpAction = (url, func) => (dispatch) => {axios.get(url).then(function(res){const action = func(res.data)
        dispatch(action)
    })
}
  • 生成 action,并发送 action
componentDidMount(){var action = getHttpAction('/getData', getInitTodoItemAction)
    // 发送函数类型的 action 时,该 action 的函数领会主动执行
    store.dispatch(action)
}

(2)应用 redux-saga 中间件

redux-saga长处:

  • 异步解耦: 异步操作被被转移到独自 saga.js 中,不再是掺杂在 action.js 或 component.js 中
  • action 解脱 thunk function: dispatch 的参数仍然是⼀个纯正的 action (FSA),⽽不是充斥“⿊魔法”thunk function
  • 异样解决: 受害于 generator function 的 saga 实现,代码异样 / 申请失败 都能够间接通过 try/catch 语法间接捕捉解决
  • 性能强⼤: redux-saga 提供了⼤量的 Saga 辅助函数和 Effect 创立器供开发者使⽤, 开发者⽆须封装或者简略封装即可使⽤
  • 灵便: redux-saga 能够将多个 Saga 能够串⾏ / 并⾏组合起来, 造成⼀个⾮常实⽤的异步 flow
  • 易测试,提供了各种 case 的测试⽅案,包含 mock task,分⽀笼罩等等

redux-saga缺点:

  • 额定的学习老本: redux-saga 不仅在使⽤难以了解的 generator function, ⽽且无数⼗个 API, 学习老本远超 redux-thunk, 最重要的是你的额定学习老本是只服务于这个库的, 与 redux-observable 不同,redux-observable 尽管也有额定学习老本然而背地是 rxjs 和⼀整套思维
  • 体积庞⼤: 体积略⼤, 代码近 2000 ⾏,min 版 25KB 左右
  • 性能过剩: 实际上并发管制等性能很难⽤到, 然而咱们仍然须要引⼊这些代码
  • ts ⽀持不敌对: yield ⽆法返回 TS 类型

redux-saga 能够捕捉 action,而后执行一个函数,那么能够把异步代码放在这个函数中,应用步骤如下:

  • 配置中间件
import {createStore, applyMiddleware, compose} from 'redux';
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga'
import TodoListSaga from './sagas'

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const sagaMiddleware = createSagaMiddleware()

const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware)
);

const store = createStore(reducer, enhancer);
sagaMiddleware.run(TodoListSaga)

export default store;
  • 将异步申请放在 sagas.js 中
import {takeEvery, put} from 'redux-saga/effects'
import {initTodoList} from './actionCreator'
import {GET_INIT_ITEM} from './actionTypes'
import axios from 'axios'

function* func(){
    try{
        // 能够获取异步返回数据
        const res = yield axios.get('/getData')
        const action = initTodoList(res.data)
        // 将 action 发送到 reducer
        yield put(action)
    }catch(e){console.log('网络申请失败')
    }
}

function* mySaga(){
    // 主动捕捉 GET_INIT_ITEM 类型的 action,并执行 func
    yield takeEvery(GET_INIT_ITEM, func)
}

export default mySaga
  • 发送 action
componentDidMount(){const action = getInitTodoItemAction()
  store.dispatch(action)
}

哪些办法会触发 React 从新渲染?从新渲染 render 会做些什么?

(1)哪些办法会触发 react 从新渲染?

  • setState()办法被调用

setState 是 React 中最罕用的命令,通常状况下,执行 setState 会触发 render。然而这里有个点值得关注,执行 setState 的时候不肯定会从新渲染。当 setState 传入 null 时,并不会触发 render。

class App extends React.Component {
  state = {a: 1};

  render() {console.log("render");
    return (
      <React.Fragement>
        <p>{this.state.a}</p>
        <button
          onClick={() => {            this.setState({ a: 1}); // 这里并没有扭转 a 的值          }}        >          Click me        </button>
        <button onClick={() => this.setState(null)}>setState null</button>
        <Child />
      </React.Fragement>
    );
  }
}
  • 父组件从新渲染

只有父组件从新渲染了,即便传入子组件的 props 未发生变化,那么子组件也会从新渲染,进而触发 render

(2)从新渲染 render 会做些什么?

  • 会对新旧 VNode 进行比照,也就是咱们所说的 Diff 算法。
  • 对新旧两棵树进行一个深度优先遍历,这样每一个节点都会一个标记,在到深度遍历的时候,每遍历到一和个节点,就把该节点和新的节点树进行比照,如果有差别就放到一个对象外面
  • 遍历差别对象,依据差别的类型,依据对应对规定更新 VNode

React 的解决 render 的根本思维模式是每次一有变动就会去从新渲染整个利用。在 Virtual DOM 没有呈现之前,最简略的办法就是间接调用 innerHTML。Virtual DOM 厉害的中央并不是说它比间接操作 DOM 快,而是说不论数据怎么变,都会尽量以最小的代价去更新 DOM。React 将 render 函数返回的虚构 DOM 树与老的进行比拟,从而确定 DOM 要不要更新、怎么更新。当 DOM 树很大时,遍历两棵树进行各种比对还是相当耗性能的,特地是在顶层 setState 一个渺小的批改,默认会去遍历整棵树。只管 React 应用高度优化的 Diff 算法,然而这个过程依然会损耗性能.

state 和 props 区别是啥?

  • state 是组件本人治理数据,管制本人的状态,可变;
  • props 是内部传入的数据参数,不可变;
  • 没有 state 的叫做无状态组件,有 state 的叫做有状态组件;
  • 多用 props,少用 state,也就是多写无状态组件。

你了解“在 React 中,一切都是组件”这句话。

组件是 React 利用 UI 的构建块。这些组件将整个 UI 分成小的独立并可重用的局部。每个组件彼此独立,而不会影响 UI 的其余部分。

应该在 React 组件的何处发动 Ajax 申请

在 React 组件中,应该在 componentDidMount 中发动网络申请。这个办法会在组件第一次“挂载”(被增加到 DOM)时执行,在组件的生命周期中仅会执行一次。更重要的是,你不能保障在组件挂载之前 Ajax 申请曾经实现,如果是这样,也就意味着你将尝试在一个未挂载的组件上调用 setState,这将不起作用。在 componentDidMount 中发动网络申请将保障这有一个组件能够更新了。

React.Children.map 和 js 的 map 有什么区别?

JavaScript 中的 map 不会对为 null 或者 undefined 的数据进行解决,而 React.Children.map 中的 map 能够解决 React.Children 为 null 或者 undefined 的状况。

正文完
 0