本文原载于SegmentFault专栏"终身学习者"
翻译自 Learn Enough React For The Interview
翻译:前端小智
整理编辑:SegmentFault
React是流行的javascript框架之一,在2019年及以后将会更加流行。React于2013年首次发布,多年来广受欢迎。它是一个声明性的、基于组件的、用于构建用户界面的高效javascript库。
以下是面试前必须了解的话题,今天我们为大家分享其中的下半部分——
什么是声明式编程
声明式编程 vs 命令式编程
什么是函数式编程
什么是组件设计模式
React 是什么
React 和 Angular 有什么不同
什么是虚拟DOM及其工作原理
什么是JSX
组件和不同类型
Props 和 State
什么是 PropTypes
如何更新状态和不更新状态
组件生命周期方法
超越继承的组合
如何在React中应用样式
什么是Redux及其工作原理
什么是React路由器及其工作原理
什么是错误边界
什么是 Fragments
什么是传送门(Portals)
什么是 Context
什么是 Hooks
如何提高性能
如何在重新加载页面时保留数据
如何从React中调用API
总结
相关阅读:
应对React面试前,必须要了解的25个话题(上)
超越继承的组合
在React中,我们总是使用组合而不是继承。我们已经在函数式编程部分讨论了什么是组合。这是一种结合简单的可重用函数来生成高阶组件的技术。下面是一个组合的例子,我们在 dashboard 组件中使用两个小组件todoForm
和todoList
。
import React from 'react';import '../App.css';import { ToDoForm } from './todoform';import { ToDolist } from './todolist';export class Dashboard extends React.Component { render() { return ( <div className="dashboard"> <ToDoForm /> <ToDolist /> div> ); }}
如何在React中应用样式
将样式应用于React组件有三种方法。
外部样式表
在此方法中,你可以将外部样式表导入到组件使用类中。 但是你应该使用className
而不是class
来为React元素应用样式, 这里有一个例子。
import React from 'react';import './App.css';import { Header } from './header/header';import { Footer } from './footer/footer';import { Dashboard } from './dashboard/dashboard';import { UserDisplay } from './userdisplay';function App() { return ( <div className="App"> <Header /> <Dashboard /> <UserDisplay /> <Footer /> div> );}export default App;
内联样式
在这个方法中,我们可以直接将 props
传递给HTML元素,属性为style
。这里有一个例子。这里需要注意的重要一点是,我们将javascript对象传递给style,这就是为什么我们使用 backgroundColor
而不是CSS方法backbackground -color
。
import React from 'react';export const Header = () => { const heading = 'TODO App' return( <div style={{backgroundColor:'orange'}}> <h1>{heading}h1> div> )}
定义样式对象并使用它
因为我们将javascript对象传递给style
属性,所以我们可以在组件中定义一个style
对象并使用它。下面是一个示例,你也可以将此对象作为 props
传递到组件树中。
import React from 'react';const footerStyle = { width: '100%', backgroundColor: 'green', padding: '50px', font: '30px', color: 'white', fontWeight: 'bold'}export const Footer = () => { return( <div style={footerStyle}> All Rights Reserved 2019 div> )}
什么是Redux及其工作原理
Redux 是 React的一个状态管理库,它基于flux。 Redux简化了React中的单向数据流。 Redux将状态管理完全从React中抽象出来。
它是如何工作的
在React中,组件连接到 redux ,如果要访问 redux,需要派出一个包含 id
和负载(payload) 的 action
。action 中的 payload
是可选的,action 将其转发给 Reducer。
当reducer
收到action
时,通过 swithc...case
语法比较 action
中type
。 匹配时,更新对应的内容返回新的 state
。
当Redux
状态更改时,连接到Redux
的组件将接收新的状态作为props
。当组件接收到这些props
时,它将进入更新阶段并重新渲染 UI。
Redux 循环细节
让我们详细看看整个redux 循环细节。
Action: Action 只是一个简单的json对象,type 和有payload作为键。type 是必须要有的,payload是可选的。下面是一个 action 的例子。
// action{ type:"SEND_EMAIL", payload: data};
Action Creators:这些是创建Actions
的函数,因此我们在派发action
时不必在组件中手动编写每个 action
。 以下是 action creator 的示例。
// action creatorexport function sendEamil(data) { return { type:"SEND_EMAIL", payload: data};}
Reducers:Reducers 是纯函数,它将 action
和当前 state
作为参数,计算必要的逻辑并返回一个新r的state
。 这些 Reducers 没有任何副作用。 它不会改变 state
而是总是返回 state
。
export default function emailReducer(state = [], action){ switch(action.type) { case "SEND_EMAIL": return Object.assign({}, state, { email: action.payload }); default: return state; }}
组件如何与 redux
进行连接
mapStateToProps:此函数将state
映射到 props
上,因此只要state
发生变化,新 state 会重新映射到 props
。 这是订阅store
的方式。
mapDispatchToProps:此函数用于将 action creators
绑定到你的props
。以便我们可以在第12
行中使用This . props.actions.sendemail()
来派发一个动作。
connect
和bindActionCreators
来自 redux。 前者用于连接 store ,如第22行,后者用于将 action creators 绑定到你的 props
,如第20行。
// import connectimport { connect } from 'react-redux'import { bindActionCreators } from 'redux'// import action creatorsimport * as userActions from '../../../actions/userActions';export class User extends React.Component { handleSubmit() { // dispatch an action this.props.actions.sendEmail(this.state.email); } }// you are mapping you state propsconst mapStateToProps = (state, ownProps) => ({user: state.user})// you are binding your action creators to your propsconst mapDispatchToProps = (dispatch) => ({actions: bindActionCreators(userActions, dispatch)})export default connect(mapStateToProps, mapDispatchToProps)(User);
什么是 React Router Dom 及其工作原理
react-router-dom
是应用程序中路由的库。 React库中没有路由功能,需要单独安装react-router-dom
。
react-router-dom 提供两个路由器BrowserRouter
和HashRoauter
。前者基于rul的pathname段,后者基于hash段。
前者:http://127.0.0.1:3000/article/num1 后者:http://127.0.0.1:3000/#/article/num1(不一定是这样,但#是少不了的)
react-router-dom 组件
BrowserRouter
和HashRouter
是路由器。Route
用于路由匹配。Link
组件用于在应用程序中创建链接。 它将在HTML中渲染为锚标记。NavLink
是突出显示当前活动链接的特殊链接。Switch
不是必需的,但在组合路由时很有用。Redirect
用于强制路由重定向
下面是组件中的Link
、NavLink
和Redirect
的例子
// normal link"/gotoA">Home// link which highlights currentlu active route with the given class name"/gotoB" activeClassName= React// you can redirect to this url"/gotoC" />
以下是 react router 组件的示例。 如果你查看下面的示例,我们将匹配路径并使用Switch
和Route
呈现相应的组件。
import React from 'react'// import react router DOM elementsimport { Switch, Route, Redirect } from 'react-router-dom'import ComponentA from '../common/compa'import ComponentB from '../common/compb'import ComponentC from '../common/compc'import ComponentD from '../common/compd'import ComponentE from '../common/compe'const Layout = ({ match }) => { return( )}export default Layout
什么是错误边界
在 React 中,我们通常有一个组件树。如果任何一个组件发生错误,它将破坏整个组件树。没有办法捕捉这些错误,我们可以用错误边界优雅地处理这些错误。
错误边界有两个作用
如果发生错误,显示回退UI
记录错误
下面是ErrorBoundary
类的一个例子。如果类实现了 getDerivedStateFromError
或componentDidCatch
这两个生命周期方法的任何一下,,那么这个类就会成为ErrorBoundary。前者返回{hasError: true}
来呈现回退UI,后者用于记录错误。
import React from 'react'export class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { // Update state so the next render will show the fallback UI. return { hasError: true }; } componentDidCatch(error, info) { // You can also log the error to an error reporting service console.log('Error::::', error); } render() { if (this.state.hasError) { // You can render any custom fallback UI return <h1>OOPS!. WE ARE LOOKING INTO IT.h1>; } return this.props.children; } }
以下是我们如何在其中一个组件中使用ErrorBoundary。使用ErrorBoundary类包裹 ToDoForm
和ToDoList
。 如果这些组件中发生任何错误,我们会记录错误并显示回退UI。
import React from 'react';import '../App.css';import { ToDoForm } from './todoform';import { ToDolist } from './todolist';import { ErrorBoundary } from '../errorboundary';export class Dashboard extends React.Component { render() { return ( <div className="dashboard"> <ErrorBoundary> <ToDoForm /> <ToDolist /> ErrorBoundary> div> ); }}
什么是 Fragments
在React中,我们需要有一个父元素,同时从组件返回React元素。有时在DOM中添加额外的节点会很烦人。使用 Fragments,我们不需要在DOM中添加额外的节点。我们只需要用 React.Fragment
或才简写 <>
来包裹内容就行了。如下 所示:
// Without Fragments return ( <div> <CompoentA /> <CompoentB /> <CompoentC /> div>)// With Fragments return ( <React.Fragment> <CompoentA /> <CompoentB /> <CompoentC /> React.Fragment> ) // shorthand notation Fragments return ( <> <CompoentA /> <CompoentB /> <CompoentC /> > )
什么是传送门(Portals)
默认情况下,所有子组件都在UI上呈现,具体取决于组件层次结构。Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
这里有一个例子。默认情况下,父组件在DOM层次结构中有子组件。
我们可以将 children
组件移出parent
组件并将其附加 id 为 someid
的 Dom 节点下。
首先,获取 id 为 someid,我们在constrcutorand中创建一个元素div,将child附加到componentDidMount中的someRoot。 最后,我们在ReactDOM.createPortal(this.props.childen),domnode的帮助下将子节点传递给该特定DOM节点。
首先,先获取 id 为someid
DOM元素,接着在构造函数中创建一个元素div,在 componentDidMount
方法中将 someRoot
放到 div 中 。 最后,通过 ReactDOM.createPortal(this.props.childen), domnode)
将 children
传递到对应的节点下。
const someRoot = document.getElementById('someid');class Modal extends React.Component { constructor(props) { super(props); this.el = document.createElement('div'); } componentDidMount() { someRoot.appendChild(this.el); } componentWillUnmount() { someRoot.removeChild(this.el); } render() { return ReactDOM.createPortal( this.props.children, this.el, ); }}
什么是上下文
有时我们必须将props
传递给组件树,即使所有中间组件都不需要这些props
。上下文是一种传递props
的方法,而不用在每一层传递组件树。
什么是 Hooks
Hooks 是React版本16.8中的新功能。 请记住,我们不能在函数组件中使用state
,因为它们不是类组件。Hooks 让我们在函数组件中可以使用state
和其他功能。
目前没有重大变化,我们不必放弃类组件。
Hook 不会影响你对 React 概念的理解。 恰恰相反,Hook 为已知的 React 概念提供了更直接的 API:props, state,context,refs 以及生命周期。稍后我们将看到,Hook 还提供了一种更强大的方式来组合他们。
我们可以使用一些钩子,例如useState,useEffect,useContext,useReducer等。
下面是 Hooks 的基本规则
Hooks 应该在外层使用,不应该在循环,条件或嵌套函数中使用
Hooks 应该只在函数组件中使用。
让我们看一个例子来理解 hooks。 这是一个函数组件,它采用props
并在UI上显示这些props
。 在useState
钩子的帮助下,我们将这个函数组件转换为有状态组件。 首先,我们在第5行定义状态,这相当于
constructor(props) { super(props); this.state = { name:'myname', age:10, address:'0000 one street' }}
useState
返回两个项,一个是user
,另一个是setUser
函数。 user
是一个可以在没有 this
关键字的情况下直接使用的对象,setUser
是一个可以用来设置用户点击第21
行按钮的状态的函数,该函数等效于以下内容。
this.setState({name:'name changed'})
1 import React, { useState } from 'react'; 2 3 export const UserDisplay = ({name, address, age}) => { 4 5 const [user, setUser] = useState({ name: 'myname', age: 10, address: '0000 onestreet' }); 6 7 return ( 8 <> 9 <div> 10 <div class="label">Name:div> 11 <div>{user.name}div> 12 div> 13 <div> 14 <div class="label">Address:div> 15 <div>{user.address}div> 16 div> 17 <div> 18 <div class="label">Age:div> 19 <div>{user.age}div> 20 div> 21 <button onClick={() => setUser({name: 'name changed'})}> 22 Click me 23 button> 24 > 25 ) 26 }
如何提高性能
我们可以通过多种方式提高应用性能,以下这些比较重要:
适当地使用
shouldComponentUpdate
生命周期方法。 它避免了子组件的不必要的渲染。 如果树中有100个组件,则不重新渲染整个组件树来提高应用程序性能。使用
create-react-app
来构建项目,这会创建整个项目结构,并进行大量优化。不可变性是提高性能的关键。不要对数据进行修改,而是始终在现有集合的基础上创建新的集合,以保持尽可能少的复制,从而提高性能。
在显示列表或表格时始终使用
Keys
,这会让 React 的更新速度更快代码分离是将代码插入到单独的文件中,只加载模块或部分所需的文件的技术。
如何在重新加载页面时保留数据
单页应用程序首先在DOM中加载index.html
,然后在用户浏览页面时加载内容,或者从同一index.html
中的后端API获取任何数据。
如果通过点击浏览器中的重新加载按钮重新加载页面index.html
,整个React应用程序将重新加载,我们将丢失应用程序的状态。 如何保留应用状态?
每当重新加载应用程序时,我们使用浏览器localstorage
来保存应用程序的状态。我们将整个存储数据保存在localstorage
中,每当有页面刷新或重新加载时,我们从localstorage
加载状态。
如何在React进行API调用
我们使用redux-thunk
在React中调用API。因为reduce
是纯函数,所以没有副作用,比如调用API。
因此,我们必须使用redux-thunk
从 action creators 那里进行 API 调用。Action creator 派发一个action,将来自API的数据放入action 的 payload
中。Reducers 接收我们在上面的redux
循环中讨论的数据,其余的过程也是相同的。
redux-thunk是一个中间件。一旦它被引入到项目中,每次派发一个action
时,都会通过thunk
传递。如果它是一个函数,它只是等待函数处理并返回响应。如果它不是一个函数,它只是正常处理。
这里有一个例子。sendEmailAPI
是从组件中调用的函数,它接受一个数据并返回一个函数,其中dispatch
作为参数。我们使用redux-thunk
调用API apiservice
,并等待收到响应。一旦接收到响应,我们就使用payload
派发一个action
。
import apiservice from '../services/apiservice';export function sendEmail(data) { return { type:"SEND_EMAIL", payload: data };}export function sendEmailAPI(email) { return function(dispatch) { return apiservice.callAPI(email).then(data => { dispatch(sendEmail(data)); }); }}
总结
要想有把握的面试,必须充分了解上述所有主题。 即使你目前正在使用React,理解这些概念也能增强你在职场中信心。
欢迎关注 SegmentFault 微信公众号 :)