乐趣区

关于前端:滴滴前端二面常考react面试题持续更新中

什么是 React 的 refs?为什么它们很重要

refs 容许你间接拜访 DOM 元素或组件实例。为了应用它们,能够向组件增加个 ref 属性。
如果该属性的值是一个回调函数,它将承受底层的 DOM 元素或组件的已挂载实例作为其第一个参数。能够在组件中存储它。

export class App extends Component {showResult() {console.log(this.input.value);
  }
  render() {
    return (
      <div>
        <input type="text" ref={(input) => (this.input = input)} />
        <button onClick={this.showResult.bind(this)}> 展现后果 </button>
      </div>
    );
  }
}

如果该属性值是一个字符串,React 将会在组件实例化对象的 refs 属性中,存储一个同名属性,该属性是对这个 DOM 元素的援用。能够通过原生的 DOM API 操作它。

export class App extends Component {showResult() {console.log(this.refs.username.value);
  }
  render() {
    return (
      <div>
        <input type="text" ref="username" />
        <button onClick={this.showResu1t.bind(this)}> 展现后果 </button>
      </div>
    );
  }
}

如何配置 React-Router 实现路由切换

(1)应用<Route> 组件

路由匹配是通过比拟 <Route> 的 path 属性和以后地址的 pathname 来实现的。当一个 <Route> 匹配胜利时,它将渲染其内容,当它不匹配时就会渲染 null。没有门路的 <Route> 将始终被匹配。

// when location = {pathname: '/about'}
<Route path='/about' component={About}/> // renders <About/>
<Route path='/contact' component={Contact}/> // renders null
<Route component={Always}/> // renders <Always/>

(2)联合应用 <Switch> 组件和 <Route> 组件

<Switch> 用于将 <Route> 分组。

<Switch>
    <Route exact path="/" component={Home} />
    <Route path="/about" component={About} />
    <Route path="/contact" component={Contact} />
</Switch>

<Switch> 不是分组 <Route> 所必须的,但他通常很有用。一个 <Switch> 会遍历其所有的子 <Route>元素,并仅渲染与以后地址匹配的第一个元素。

(3)应用 <Link>、<NavLink>、<Redirect> 组件

<Link> 组件来在你的应用程序中创立链接。无论你在何处渲染一个<Link>,都会在应用程序的 HTML 中渲染锚(<a>)。

<Link to="/">Home</Link>   
// <a href='/'>Home</a>

是一种非凡类型的 当它的 to 属性与以后地址匹配时,能够将其定义为 ” 沉闷的 ”。

// location = {pathname: '/react'}
<NavLink to="/react" activeClassName="hurray">
    React
</NavLink>
// <a href='/react' className='hurray'>React</a>

当咱们想强制导航时,能够渲染一个 <Redirect>,当一个<Redirect> 渲染时,它将应用它的 to 属性进行定向。

setState 之后 产生了什么?

  • (1)代码中调用 setState 函数之后,React 会将传入的参数对象与组件以后的状态合并,而后触发所谓的和谐过程(Reconciliation)。
  • (2)通过和谐过程,React 会以绝对高效的形式依据新的状态构建 React 元素树并且着手从新渲染整个 UI 界面;
  • (3)在 React 失去元素树之后,React 会主动计算出新的树与老树的节点差别,而后依据差别对界面进行最小化重渲染;
  • (4)在差别计算算法中,React 可能绝对准确地晓得哪些地位产生了扭转以及应该如何扭转,这就保障了按需更新,而不是全副从新渲染。

setState 的调用会引起 React 的更新生命周期的 4 个函数执行。

shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate

虚构 DOM 的引入与间接操作原生 DOM 相比,哪一个效率更高,为什么

虚构 DOM 绝对原生的 DOM 不肯定是效率更高,如果只批改一个按钮的文案,那么虚构 DOM 的操作无论如何都不可能比实在的 DOM 操作更快。在首次渲染大量 DOM 时,因为多了一层虚构 DOM 的计算,虚构 DOM 也会比 innerHTML 插入慢。它能保障性能上限,在实在 DOM 操作的时候进行针对性的优化时,还是更快的。所以要依据具体的场景进行探讨。

在整个 DOM 操作的演化过程中,其实主要矛盾并不在于性能,而在于开发者写得爽不爽,在于研发体验 / 研发效率。虚构 DOM 不是别的,正是前端开发们为了谋求更好的研发体验和研发效率而发明进去的高阶产物。虚构 DOM 并不一定会带来更好的性能,React 官网也素来没有把虚构 DOM 作为性能层面的卖点对外输入过。** 虚构 DOM 的优越之处在于,它可能在提供更爽、更高效的研发模式(也就是函数式的 UI 编程形式)的同时,依然放弃一个还不错的性能。

React-Router 4 的 Switch 有什么用?

Switch 通常被用来包裹 Route,用于渲染与门路匹配的第一个子 <Route><Redirect>,它外面不能放其余元素。

如果不加 <Switch>

import {Route} from 'react-router-dom'

<Route path="/" component={Home}></Route>
<Route path="/login" component={Login}></Route>

Route 组件的 path 属性用于匹配门路,因为须要匹配 /Home,匹配 /loginLogin,所以须要两个 Route,然而不能这么写。这样写的话,当 URL 的 path 为“/login”时,<Route path="/" /><Route path="/login" /> 都会被匹配,因而页面会展现 Home 和 Login 两个组件。这时就须要借助 <Switch> 来做到只显示一个匹配组件:

import {Switch, Route} from 'react-router-dom'

<Switch>
    <Route path="/" component={Home}></Route>
    <Route path="/login" component={Login}></Route>
</Switch>

此时,再拜访“/login”门路时,却只显示了 Home 组件。这是就用到了 exact 属性,它的作用就是准确匹配门路,常常与<Switch> 联结应用。只有当 URL 和该 <Route> 的 path 属性完全一致的状况下能力匹配上:

import {Switch, Route} from 'react-router-dom'

<Switch>
   <Route exact path="/" component={Home}></Route>
   <Route exact path="/login" component={Login}></Route>
</Switch>

为什么 useState 要应用数组而不是对象

useState 的用法:

const [count, setCount] = useState(0)

能够看到 useState 返回的是一个数组,那么为什么是返回数组而不是返回对象呢?

这里用到了解构赋值,所以先来看一下 ES6 的解构赋值:

数组的解构赋值
const foo = [1, 2, 3];
const [one, two, three] = foo;
console.log(one);    // 1
console.log(two);    // 2
console.log(three);    // 3
对象的解构赋值
const user = {
  id: 888,
  name: "xiaoxin"
};
const {id, name} = user;
console.log(id);    // 888
console.log(name);    // "xiaoxin"

看完这两个例子,答案应该就进去了:

  • 如果 useState 返回的是数组,那么使用者能够对数组中的元素命名,代码看起来也比拟洁净
  • 如果 useState 返回的是对象,在解构对象的时候必须要和 useState 外部实现返回的对象同名,想要应用屡次的话,必须得设置别名能力应用返回值

上面来看看如果 useState 返回对象的状况:

// 第一次应用
const {state, setState} = useState(false);
// 第二次应用
const {state: counter, setState: setCounter} = useState(0) 

这里能够看到,返回对象的应用形式还是挺麻烦的,更何况理论我的项目中会应用的更频繁。总结:useState 返回的是 array 而不是 object 的起因就是为了 升高应用的复杂度,返回数组的话能够间接依据程序解构,而返回对象的话要想应用屡次就须要定义别名了。

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

fetch 封装

npm install whatwg-fetch --save  // 适配其余浏览器
npm install es6-promise

export const handleResponse = (response) => {if (response.status === 403 || response.status === 401) {const oauthurl = response.headers.get('locationUrl');
    if (!_.isEmpty(oauthUrl)) {
      window.location.href = oauthurl;
      return;
    }
  }
  if (!response.ok) {return getErrorMessage(response).then(errorMessage => apiError(response.status, errorMessage));
  }
  if (isJson(response)) {return response.json();
  }
  if (isText(response)) {return response.text();
  }

  return response.blob();};

const httpRequest = {
  request: ({method, headers, body, path, query,}) => {const options = {};
    let url = path;
    if (method) {options.method = method;}
    if (headers) {options.headers = {...options.headers,...headers};
    }
    if (body) {options.body = body;}
    if (query) {const params = Object.keys(query)
        .map(k => `${k}=${query[k]}`)
        .join('&');
      url = url.concat(`?${params}`);
    }
    return fetch(url, Object.assign({}, options, {credentials: 'same-origin'})).then(handleResponse);
  },
};

export default httpRequest;

React 高阶组件是什么,和一般组件有什么区别,实用什么场景

官网解释∶

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 本身不是 React API 的一部分,它是一种基于 React 的组合个性而造成的设计模式。

高阶组件(HOC)就是一个函数,且该函数承受一个组件作为参数,并返回一个新的组件,它只是一种组件的设计模式,这种设计模式是由 react 本身的组合性质必然产生的。咱们将它们称为纯组件,因为它们能够承受任何动静提供的子组件,但它们不会批改或复制其输出组件中的任何行为。

// hoc 的定义
function withSubscription(WrappedComponent, selectData) {
  return class extends React.Component {constructor(props) {super(props);
      this.state = {data: selectData(DataSource, props)
      };
    }
    // 一些通用的逻辑解决
    render() {
      // ... 并应用新数据渲染被包装的组件!
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };

// 应用
const BlogPostWithSubscription = withSubscription(BlogPost,
  (DataSource, props) => DataSource.getBlogPost(props.id));

1)HOC 的优缺点

  • 长处∶ 逻辑服用、不影响被包裹组件的外部逻辑。
  • 毛病∶hoc 传递给被包裹组件的 props 容易和被包裹后的组件重名,进而被笼罩

2)实用场景

  • 代码复用,逻辑形象
  • 渲染劫持
  • State 形象和更改
  • Props 更改

3)具体利用例子

  • 权限管制: 利用高阶组件的 条件渲染 个性能够对页面进行权限管制,权限管制个别分为两个维度:页面级别和 页面元素级别
// HOC.js
function withAdminAuth(WrappedComponent) {
    return class extends React.Component {
        state = {isAdmin: false,}
        async UNSAFE_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...
    }
    UNSAFE_componentWillMount() {// fetching data}
    render() {// render page with data}
}
export default withAdminAuth(PageA);


// pages/page-b.js
class PageB extends React.Component {constructor(props) {super(props);
    // something here...
        }
    UNSAFE_componentWillMount() {// fetching data}
    render() {// render page with data}
}
export default withAdminAuth(PageB);
  • 组件渲染性能追踪: 借助父组件子组件生命周期规定捕捉子组件的生命周期,能够不便的对某个组件的渲染工夫进行记录∶
class Home extends React.Component {render() {return (<h1>Hello World.</h1>);
        }
    }
    function withTiming(WrappedComponent) {
        return class extends WrappedComponent {constructor(props) {super(props);
                this.start = 0;
                this.end = 0;
            }
            UNSAFE_componentWillMount() {super.componentWillMount && super.componentWillMount();
                this.start = Date.now();}
            componentDidMount() {super.componentDidMount && super.componentDidMount();
                this.end = Date.now();
                console.log(`${WrappedComponent.name} 组件渲染工夫为 ${this.end - this.start} ms`);
            }
            render() {return super.render();
            }
        };
    }

    export default withTiming(Home);   


留神:withTiming 是利用 反向继承 实现的一个高阶组件,性能是计算被包裹组件(这里是 Home 组件)的渲染工夫。

  • 页面复用
const withFetching = fetching => WrappedComponent => {
    return class extends React.Component {
        state = {data: [],
        }
        async UNSAFE_componentWillMount() {const data = await fetching();
            this.setState({data,});
        }
        render() {return <WrappedComponent data={this.state.data} {...this.props} />;
        }
    }
}

// pages/page-a.js
export default withFetching(fetching('science-fiction'))(MovieList);
// pages/page-b.js
export default withFetching(fetching('action'))(MovieList);
// pages/page-other.js
export default withFetching(fetching('some-other-type'))(MovieList);

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 中 keys 的作用是什么?

Keys 是 React 用于追踪哪些列表中元素被批改、被增加或者被移除的辅助标识。

在 React 中渲染汇合时,向每个反复的元素增加关键字对于帮忙 React 跟踪元素与数据之间的关联十分重要。key 应该是惟一 ID,最好是 UUID 或收集项中的其余惟一字符串:

<ul>
  {todos.map((todo) =>
    <li key={todo.id}>
      {todo.text}
    </li>
  )};
</ul>

在汇合中增加和删除我的项目时,不应用键或将索引用作键会导致奇怪的行为。

React 的状态晋升是什么?应用场景有哪些?

React 的状态晋升就是用户对子组件操作,子组件不扭转本人的状态,通过本人的 props 把这个操作扭转的数据传递给父组件,扭转父组件的状态,从而扭转受父组件管制的所有子组件的状态,这也是 React 单项数据流的个性决定的。官网的原话是:共享 state(状态) 是通过将其挪动到须要它的组件的最靠近的独特先人组件来实现的。这被称为“状态晋升(Lifting State Up)”。

概括来说就是 将多个组件须要共享的状态晋升到它们最近的父组件上 在父组件上扭转这个状态而后通过 props 分发给子组件。

一个简略的例子,父组件中有两个 input 子组件,如果想在第一个输入框输出数据,来扭转第二个输入框的值,这就须要用到状态晋升。

class Father extends React.Component {constructor(props) {super(props)
        this.state = {
            Value1: '',
            Value2: ''
        }
    }
    value1Change(aa) {
        this.setState({Value1: aa})
    }
    value2Change(bb) {
        this.setState({Value2: bb})
    }
    render() {
        return (<div style={{ padding: "100px"}}>
                <Child1 value1={this.state.Value1} onvalue1Change={this.value1Change.bind(this)} />
                                <Child2 value2={this.state.Value1} />
            </div>
        )
    }
}
class Child1 extends React.Component {constructor(props) {super(props)
    }
    changeValue(e) {this.props.onvalue1Change(e.target.value)
    }
    render() {
        return (<input value={this.props.Value1} onChange={this.changeValue.bind(this)} />
        )
    }
}
class Child2 extends React.Component {constructor(props) {super(props)
    }
    render() {
        return (<input value={this.props.value2} />
        )
    }
}

ReactDOM.render(
    <Father />,
    document.getElementById('root')
)

React 中的 setState 批量更新的过程是什么?

调用 setState 时,组件的 state 并不会立刻扭转,setState 只是把要批改的 state 放入一个队列,React 会优化真正的执行机会,并出于性能起因,会将 React 事件处理程序中的屡次React 事件处理程序中的屡次 setState 的状态批改合并成一次状态批改。最终更新只产生一次组件及其子组件的从新渲染,这对于大型应用程序中的性能晋升至关重要。

this.setState({count: this.state.count + 1    ===>    入队,[count+ 1 的工作]
});
this.setState({count: this.state.count + 1    ===>    入队,[count+ 1 的工作,count+ 1 的工作]
});
                                          ↓
                                         合并 state,[count+ 1 的工作]
                                          ↓
                                         执行 count+ 1 的工作

须要留神的是,只有同步代码还在执行,“攒起来”这个动作就不会进行。(注:这里之所以屡次 +1 最终只有一次失效,是因为在同一个办法中屡次 setState 的合并动作不是单纯地将更新累加。比方这里对于雷同属性的设置,React 只会为其保留最初一次的更新)。

react 父子传值

父传子——在调用子组件上绑定,子组件中获取 this.props
子传父——援用子组件的时候传过来一个办法,子组件通过 this.props.methed()传过来参数

connection

react router

import React from 'react'
import {render} from 'react-dom'
import {browserHistory, Router, Route, IndexRoute} from 'react-router'

import App from '../components/App'
import Home from '../components/Home'
import About from '../components/About'
import Features from '../components/Features'

render(<Router history={browserHistory}>  // history 路由
    <Route path='/' component={App}>
      <IndexRoute component={Home} />
      <Route path='about' component={About} />
      <Route path='features' component={Features} />
    </Route>
  </Router>,
  document.getElementById('app')
)
render(<Router history={browserHistory} routes={routes} />,
  document.getElementById('app')
)

React Router 提供一个 routerWillLeave 生命周期钩子,这使得 React 组件能够拦挡正在产生的跳转,或在来到 route 前提醒用户。routerWillLeave 返回值有以下两种:

return false 勾销此次跳转
return 返回提示信息,在来到 route 前提醒用户进行确认。

React-Router 的路由有几种模式?

React-Router 反对应用 hash(对应 HashRouter)和 browser(对应 BrowserRouter)两种路由规定,react-router-dom 提供了 BrowserRouter 和 HashRouter 两个组件来实现利用的 UI 和 URL 同步:

  • BrowserRouter 创立的 URL 格局:xxx.com/path
  • HashRouter 创立的 URL 格局:xxx.com/#/path

(1)BrowserRouter

它应用 HTML5 提供的 history API(pushState、replaceState 和 popstate 事件)来放弃 UI 和 URL 的同步。由此能够看出,BrowserRouter 是应用 HTML 5 的 history API 来管制路由跳转的:

<BrowserRouter
    basename={string}
    forceRefresh={bool}
    getUserConfirmation={func}
    keyLength={number}
/>

其中的属性如下:

  • basename 所有路由的基准 URL。basename 的正确格局是后面有一个前导斜杠,但不能有尾部斜杠;
<BrowserRouter basename="/calendar">
    <Link to="/today" />
</BrowserRouter>

等同于

<a href="/calendar/today" />
  • forceRefresh 如果为 true,在导航的过程中整个页面将会刷新。个别状况下,只有在不反对 HTML5 history API 的浏览器中应用此性能;
  • getUserConfirmation 用于确认导航的函数,默认应用 window.confirm。例如,当从 /a 导航至 /b 时,会应用默认的 confirm 函数弹出一个提醒,用户点击确定后才进行导航,否则不做任何解决;
// 这是默认的确认函数
const getConfirmation = (message, callback) => {const allowTransition = window.confirm(message);
  callback(allowTransition);
}
<BrowserRouter getUserConfirmation={getConfirmation} />

须要配合<Prompt> 一起应用。

  • KeyLength 用来设置 Location.Key 的长度。

(2)HashRouter

应用 URL 的 hash 局部(即 window.location.hash)来放弃 UI 和 URL 的同步。由此能够看出,HashRouter 是通过 URL 的 hash 属性来管制路由跳转的:

<HashRouter
    basename={string}
    getUserConfirmation={func}
    hashType={string}  
/>

其参数如下

  • basename, getUserConfirmation 和 BrowserRouter 性能一样;
  • hashType window.location.hash 应用的 hash 类型,有如下几种:

    • slash – 前面跟一个斜杠,例如 #/ 和 #/sunshine/lollipops;
    • noslash – 前面没有斜杠,例如 # 和 #sunshine/lollipops;
    • hashbang – Google 格调的 ajax crawlable,例如 #!/ 和 #!/sunshine/lollipops。

在 React 中页面从新加载时怎么保留数据?

这个问题就设计到了 数据长久化, 次要的实现形式有以下几种:

  • Redux: 将页面的数据存储在 redux 中,在从新加载页面时,获取 Redux 中的数据;
  • data.js: 应用 webpack 构建的我的项目,能够建一个文件,data.js,将数据保留 data.js 中,跳转页面后获取;
  • sessionStorge: 在进入抉择地址页面之前,componentWillUnMount 的时候,将数据存储到 sessionStorage 中,每次进入页面判断 sessionStorage 中有没有存储的那个值,有,则读取渲染数据;没有,则阐明数据是初始化的状态。返回或进入除了抉择地址以外的页面,清掉存储的 sessionStorage,保障下次进入是初始化的数据
  • history API: History API 的 pushState 函数能够给历史记录关联一个任意的可序列化 state,所以能够在路由 push 的时候将以后页面的一些信息存到 state 中,下次返回到这个页面的时候就能从 state 外面取出来到前的数据从新渲染。react-router 间接能够反对。这个办法适宜一些须要长期存储的场景。

React.forwardRef 是什么?它有什么作用?

React.forwardRef 会创立一个 React 组件,这个组件可能将其承受的 ref 属性转发到其组件树下的另一个组件中。这种技术并不常见,但在以下两种场景中特地有用:

  • 转发 refs 到 DOM 组件
  • 在高阶组件中转发 refs

React 组件命名举荐的形式是哪个?

通过援用而不是应用来命名组件 displayName。

应用 displayName 命名组件:

export default React.createClass({displayName: 'TodoApp',  // ...})

React 举荐的办法:

export default class TodoApp extends React.Component {// ...}

什么是 Props

Props 是 React 中属性的简写。它们是只读组件,必须放弃纯,即不可变。它们总是在整个利用中从父组件传递到子组件。子组件永远不能将 prop 送回父组件。这有助于保护单向数据流,通常用于出现动静生成的数据。

setState 是同步异步?为什么?实现原理?

1. setState 是同步执行的

setState 是同步执行的,然而 state 并不一定会同步更新

2. setState 在 React 生命周期和合成事件中批量笼罩执行

在 React 的生命周期钩子和合成事件中,屡次执行 setState,会批量执行

具体表现为,屡次同步执行的 setState,会进行合并,相似于 Object.assign,雷同的 key,前面的会笼罩后面的

当遇到多个 setState 调用时候,会提取单次传递 setState 的对象,把他们合并在一起造成一个新的
繁多对象,并用这个繁多的对象去做 setState 的事件,就像 Object.assign 的对象合并,后一个
key 值会笼罩后面的 key 值

通过 React 解决的事件是不会同步更新 this.state 的. 通过 addEventListener || setTimeout/setInterval 的形式解决的则会同步更新。
为了合并 setState,咱们须要一个队列来保留每次 setState 的数据,而后在一段时间后执行合并操作和更新 state,并清空这个队列,而后渲染组件。

退出移动版