共计 12821 个字符,预计需要花费 33 分钟才能阅读完成。
Component, Element, Instance 之间有什么区别和分割?
- 元素: 一个元素
element
是一个一般对象 (plain object),形容了对于一个 DOM 节点或者其余组件component
,你想让它在屏幕上出现成什么样子。元素element
能够在它的属性props
中蕴含其余元素 (译注: 用于造成元素树)。创立一个 React 元素element
老本很低。元素element
创立之后是不可变的。 - 组件: 一个组件
component
能够通过多种形式申明。能够是带有一个render()
办法的类,简略点也能够定义为一个函数。这两种状况下,它都把属性props
作为输出,把返回的一棵元素树作为输入。 - 实例: 一个实例
instance
是你在所写的组件类component class
中应用关键字this
所指向的货色(译注: 组件实例)。它用来存储本地状态和响应生命周期事件很有用。
函数式组件 (Functional component
) 基本没有实例 instance
。类组件(Class component
) 有实例instance
,然而永远也不须要间接创立一个组件的实例,因为 React 帮咱们做了这些。
connect 原理
- 首先
connect
之所以会胜利,是因为Provider
组件: - 在原利用组件上包裹一层,使原来整个利用成为
Provider
的子组件 接管Redux
的store
作为props
,通过context
对象传递给子孙组件上的connect
connect
做了些什么。它真正连贯Redux
和React
,它包在咱们的容器组件的外一层,它接管下面Provider
提供的store
外面的state
和dispatch
,传给一个构造函数,返回一个对象,以属性模式传给咱们的容器组件
connect
是一个高阶函数,首先传入mapStateToProps
、mapDispatchToProps
,而后返回一个生产Component
的函数 (wrapWithConnect
),而后再将真正的Component
作为参数传入wrapWithConnect
,这样就生产出一个通过包裹的Connect
组件,
该组件具备如下特点
- 通过
props.store
获取先人Component
的store props
包含stateProps
、dispatchProps
、parentProps
, 合并在一起失去nextState
,作为props
传给真正的Component componentDidMount
时,增加事件this.store.subscribe(this.handleChange)
,实现页面交互 shouldComponentUpdate
时判断是否有防止进行渲染,晋升页面性能,并失去nextState
componentWillUnmount
时移除注册的事件this.handleChange
因为
connect
的源码过长,咱们只看次要逻辑
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {return function wrapWithConnect(WrappedComponent) {
class Connect extends Component {constructor(props, context) {
// 从先人 Component 处取得 store
this.store = props.store || context.store
this.stateProps = computeStateProps(this.store, props)
this.dispatchProps = computeDispatchProps(this.store, props)
this.state = {storeState: null}
// 对 stateProps、dispatchProps、parentProps 进行合并
this.updateState()}
shouldComponentUpdate(nextProps, nextState) {
// 进行判断,当数据产生扭转时,Component 从新渲染
if (propsChanged || mapStateProducedChange || dispatchPropsChanged) {this.updateState(nextProps)
return true
}
}
componentDidMount() {
// 扭转 Component 的 state
this.store.subscribe(() = {
this.setState({storeState: this.store.getState()
})
})
}
render() {
// 生成包裹组件 Connect
return (<WrappedComponent {...this.nextState} />
)
}
}
Connect.contextTypes = {store: storeShape}
return Connect;
}
}
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
:若为真,重定向操作将会把新地址退出到拜访历史记录外面,并且无奈回退到后面的页面。
Redux 中间件是怎么拿到 store 和 action? 而后怎么解决?
redux 中间件实质就是一个函数柯里化。redux applyMiddleware Api 源码中每个 middleware 承受 2 个参数,Store 的 getState 函数和 dispatch 函数,别离取得 store 和 action,最终返回一个函数。该函数会被传入 next 的下一个 middleware 的 dispatch 办法,并返回一个接管 action 的新函数,这个函数能够间接调用 next(action),或者在其余须要的时刻调用,甚至基本不去调用它。调用链中最初一个 middleware 会承受实在的 store 的 dispatch 办法作为 next 参数,并借此完结调用链。所以,middleware 的函数签名是({getState,dispatch})=> next => action。
参考:前端 react 面试题具体解答
react 中的 Portal 是什么?
Portals 提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的形式。
第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或碎片。
第二个参数(container)则是一个 DOM 元素。
ReactDOM.createPortal(child, container)
useEffect 与 useLayoutEffect 的区别
(1)共同点
- 使用成果: useEffect 与 useLayoutEffect 两者都是用于解决副作用,这些副作用包含扭转 DOM、设置订阅、操作定时器等。在函数组件外部操作副作用是不被容许的,所以须要应用这两个函数去解决。
- 应用形式: useEffect 与 useLayoutEffect 两者底层的函数签名是完全一致的,都是调用的 mountEffectImpl 办法,在应用上也没什么差别,根本能够间接替换。
(2)不同点
- 应用场景: useEffect 在 React 的渲染过程中是被异步调用的,用于绝大多数场景;而 useLayoutEffect 会在所有的 DOM 变更之后同步调用,次要用于解决 DOM 操作、调整款式、防止页面闪动等问题。也正因为是同步解决,所以须要防止在 useLayoutEffect 做计算量较大的耗时工作从而造成阻塞。
- 应用成果: useEffect 是依照程序执行代码的,扭转屏幕像素之后执行(先渲染,后扭转 DOM),当扭转屏幕内容时可能会产生闪动;useLayoutEffect 是扭转屏幕像素之前就执行了(会推延页面显示的事件,先扭转 DOM 后渲染),不会产生闪动。useLayoutEffect 总是比 useEffect 先执行。
在将来的趋势上,两个 API 是会长期共存的,临时没有删减合并的打算,须要开发者依据场景去自行抉择。React 团队的倡议十分实用,如果切实分不清,先用 useEffect,个别问题不大;如果页面有异样,再间接替换为 useLayoutEffect 即可。
在 React 中如何防止不必要的 render?
React 基于虚构 DOM 和高效 Diff 算法的完满配合,实现了对 DOM 最小粒度的更新。大多数状况下,React 对 DOM 的渲染效率足以业务日常。但在个别简单业务场景下,性能问题仍然会困扰咱们。此时须要采取一些措施来晋升运行性能,其很重要的一个方向,就是防止不必要的渲染(Render)。这里提下优化的点:
- shouldComponentUpdate 和 PureComponent
在 React 类组件中,能够利用 shouldComponentUpdate 或者 PureComponent 来缩小因父组件更新而触发子组件的 render,从而达到目标。shouldComponentUpdate 来决定是否组件是否从新渲染,如果不心愿组件从新渲染,返回 false 即可。
- 利用高阶组件
在函数组件中,并没有 shouldComponentUpdate 这个生命周期,能够利用高阶组件,封装一个相似 PureComponet 的性能
- 应用 React.memo
React.memo 是 React 16.6 新的一个 API,用来缓存组件的渲染,防止不必要的更新,其实也是一个高阶组件,与 PureComponent 非常相似,但不同的是,React.memo 只能用于函数组件。
为什么列表循环渲染的 key 最好不要用 index
举例说明
变动前数组的值是[1,2,3,4],key 就是对应的下标:0,1,2,3
变动后数组的值是[4,3,2,1],key 对应的下标也是:0,1,2,3
- 那么 diff 算法在变动前的数组找到 key = 0 的值是 1,在变动后数组里找到的 key= 0 的值是 4
- 因为子元素不一样就从新删除并更新
- 然而如果加了惟一的 key, 如下
变动前数组的值是[1,2,3,4],key 就是对应的下标:id0,id1,id2,id3
变动后数组的值是[4,3,2,1],key 对应的下标也是:id3,id2,id1,id0
- 那么 diff 算法在变动前的数组找到 key =id0 的值是 1,在变动后数组里找到的 key=id0 的值也是 1
- 因为子元素雷同,就不删除并更新,只做挪动操作,这就晋升了性能
什么是高阶组件
高阶组件不是组件,是 加强函数,能够输出一个元组件,返回出一个新的加强组件
- 属性代理 (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();
}
}
}
为什么 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 的起因就是为了 升高应用的复杂度,返回数组的话能够间接依据程序解构,而返回对象的话要想应用屡次就须要定义别名了。
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 封装的状态管理工具。
什么是上下文 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'));
Redux 状态管理器和变量挂载到 window 中有什么区别
两者都是存储数据以供前期应用。然而 Redux 状态更改可回溯——Time travel,数据多了的时候能够很清晰的晓得改变在哪里产生,残缺的提供了一套状态管理模式。
随着 JavaScript 单页利用开发日趋简单,JavaScript 须要治理比任何时候都要多的 state(状态)。这些 state 可能包含服务器响应、缓存数据、本地生成尚未长久化到服务器的数据,也包含 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。
治理一直变动的 state 十分艰难。如果一个 model 的变动会引起另一个 model 变动,那么当 view 变动时,就可能引起对应 model 以及另一个 model 的变动,顺次地,可能会引起另一个 view 的变动。直至你搞不清楚到底产生了什么。state 在什么时候,因为什么起因,如何变动未然不受管制。当零碎变得盘根错节的时候,想重现问题或者增加新性能就会变得举步维艰。
如果这还不够蹩脚,思考一些来自前端开发畛域的新需要,如更新调优、服务端渲染、路由跳转前申请数据等等。前端开发者正在禁受前所未有的复杂性,难道就这么放弃了吗? 当然不是。
这里的复杂性很大水平上来自于:咱们总是将两个难以理清的概念混同在一起:变动和异步。能够称它们为曼妥思和可乐。如果把二者离开,能做的很好,但混到一起,就变得一团糟。一些库如 React 视图在视图层禁止异步和间接操作 DOM 来解决这个问题。美中不足的是,React 仍旧把解决 state 中数据的问题留给了你。Redux 就是为了帮你解决这个问题。
React 必须应用 JSX 吗?
React 并不强制要求应用 JSX。当不想在构建环境中配置无关 JSX 编译时,不在 React 中应用 JSX 会更加不便。
每个 JSX 元素只是调用 React.createElement(component, props, ...children)
的语法糖。因而,应用 JSX 能够实现的任何事件都能够通过纯 JavaScript 实现。
例如,用 JSX 编写的代码:
class Hello extends React.Component {render() {return <div>Hello {this.props.toWhat}</div>;
}
}
ReactDOM.render(
<Hello toWhat="World" />,
document.getElementById('root')
);
能够编写为不应用 JSX 的代码:
class Hello extends React.Component {render() {return React.createElement('div', null, `Hello ${this.props.toWhat}`);
}
}
ReactDOM.render(React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
React 中发动网络申请应该在哪个生命周期中进行?为什么?
对于异步申请,最好放在 componentDidMount 中去操作,对于同步的状态扭转,能够放在 componentWillMount 中,个别用的比拟少。
如果认为在 componentWillMount 里发动申请能提前取得后果,这种想法其实是谬误的,通常 componentWillMount 比 componentDidMount 早不了多少微秒,网络上任何一点提早,这一点差别都可忽略不计。
react 的生命周期: constructor() -> componentWillMount() -> render() -> componentDidMount()
下面这些办法的调用是有秩序的,由上而下顺次调用。
- constructor 被调用是在组件筹备要挂载的最开始,此时组件尚未挂载到网页上。
- componentWillMount 办法的调用在 constructor 之后,在 render 之前,在这办法里的代码调用 setState 办法不会触发从新 render,所以它个别不会用来作加载数据之用。
- componentDidMount 办法中的代码,是在组件曾经齐全挂载到网页上才会调用被执行,所以能够保证数据的加载。此外,在这办法中调用 setState 办法,会触发从新渲染。所以,官网设计这个办法就是用来加载内部数据用的,或解决其余的副作用代码。与组件上的数据无关的加载,也能够在 constructor 里做,但 constructor 是做组件 state 初绐化工作,并不是做加载数据这工作的,constructor 里也不能 setState,还有加载的工夫太长或者出错,页面就无奈加载进去。所以有副作用的代码都会集中在 componentDidMount 办法里。
总结:
- 跟服务器端渲染(同构)有关系,如果在 componentWillMount 外面获取数据,fetch data 会执行两次,一次在服务器端一次在客户端。在 componentDidMount 中能够解决这个问题,componentWillMount 同样也会 render 两次。
- 在 componentWillMount 中 fetch data,数据肯定在 render 后能力达到,如果遗记了设置初始状态,用户体验不好。
- react16.0 当前,componentWillMount 可能会被执行屡次。
组件通信的形式有哪些
- ⽗组件向⼦组件通信: ⽗组件能够向⼦组件通过传 props 的⽅式,向⼦组件进⾏通信
- ⼦组件向⽗组件通信: props+ 回调的⽅式,⽗组件向⼦组件传递 props 进⾏通信,此 props 为作⽤域为⽗组件⾃身的函 数,⼦组件调⽤该函数,将⼦组件想要传递的信息,作为参数,传递到⽗组件的作⽤域中
- 兄弟组件通信: 找到这两个兄弟节点独特的⽗节点, 联合上⾯两种⽅式由⽗节点转发信息进⾏通信
- 跨层级通信: Context 设计⽬的是为了共享那些对于⼀个组件树⽽⾔是“全局”的数据,例如以后认证的⽤户、主题或⾸选语⾔,对于逾越多层的全局数据通过 Context 通信再适宜不过
- 公布订阅模式: 发布者公布事件,订阅者监听事件并做出反馈, 咱们能够通过引⼊ event 模块进⾏通信
- 全局状态治理⼯具: 借助 Redux 或者 Mobx 等全局状态治理⼯具进⾏通信, 这种⼯具会保护⼀个全局状态中⼼ Store, 并依据不同的事件产⽣新的状态
React 中 keys 的作用是什么?
Keys 是 React 用于追踪哪些列表中元素被批改、被增加或者被移除的辅助标识。
在 React 中渲染汇合时,向每个反复的元素增加关键字对于帮忙 React 跟踪元素与数据之间的关联十分重要。key 应该是惟一 ID,最好是 UUID 或收集项中的其余惟一字符串:
<ul>
{todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
)};
</ul>
在汇合中增加和删除我的项目时,不应用键或将索引用作键会导致奇怪的行为。
在 React 中组件的 props 扭转时更新组件的有哪些办法?
在一个组件传入的 props 更新时从新渲染该组件罕用的办法是在 componentWillReceiveProps
中将新的 props 更新到组件的 state 中(这种 state 被成为派生状态(Derived State)),从而实现从新渲染。React 16.3 中还引入了一个新的钩子函数 getDerivedStateFromProps
来专门实现这一需要。
(1)componentWillReceiveProps(已废除)
在 react 的 componentWillReceiveProps(nextProps)生命周期中,能够在子组件的 render 函数执行前,通过 this.props 获取旧的属性,通过 nextProps 获取新的 props,比照两次 props 是否雷同,从而更新子组件本人的 state。
这样的益处是,能够将数据申请放在这里进行执行,须要传的参数则从 componentWillReceiveProps(nextProps)中获取。而不用将所有的申请都放在父组件中。于是该申请只会在该组件渲染时才会收回,从而加重申请累赘。
(2)getDerivedStateFromProps(16.3 引入)
这个生命周期函数是为了代替 componentWillReceiveProps
存在的,所以在须要应用 componentWillReceiveProps
时,就能够思考应用 getDerivedStateFromProps
来进行代替。
两者的参数是不雷同的,而 getDerivedStateFromProps
是一个动态函数,也就是这个函数不能通过 this 拜访到 class 的属性,也并不举荐间接拜访属性。而是应该通过参数提供的 nextProps 以及 prevState 来进行判断,依据新传入的 props 来映射到 state。
须要留神的是,如果 props 传入的内容不须要影响到你的 state,那么就须要返回一个 null,这个返回值是必须的,所以尽量将其写到函数的开端:
static getDerivedStateFromProps(nextProps, prevState) {const {type} = nextProps;
// 当传入的 type 发生变化的时候,更新 state
if (type !== prevState.type) {
return {type,};
}
// 否则,对于 state 不进行任何操作
return null;
}
对 React-Intl 的了解,它的工作原理?
React-intl 是雅虎的语言国际化开源我的项目 FormatJS 的一部分,通过其提供的组件和 API 能够与 ReactJS 绑定。
React-intl 提供了两种应用办法,一种是援用 React 组件,另一种是间接调取 API,官网更加举荐在 React 我的项目中应用前者,只有在无奈应用 React 组件的中央,才应该调用框架提供的 API。它提供了一系列的 React 组件,包含数字格式化、字符串格式化、日期格式化等。
在 React-intl 中,能够配置不同的语言包,他的工作原理就是依据须要,在语言包之间进行切换。
类组件和函数组件有何不同?
解答
在 React 16.8 版本(引入钩子)之前,应用基于类的组件来创立须要保护外部状态或利用生命周期办法的组件(即 componentDidMount
和shouldComponentUpdate
)。基于类的组件是 ES6 类,它扩大了 React 的 Component 类,并且至多实现了 render()
办法。
类组件:
class Welcome extends React.Component {render() {return <h1>Hello, {this.props.name}</h1>;
}
}
函数组件是无状态的(同样,小于 React 16.8 版本),并返回要出现的输入。它们渲染 UI 的首选只依赖于属性,因为它们比基于类的组件更简略、更具性能。
函数组件:
function Welcome(props) {return <h1>Hello, {props.name}</h1>;
}
留神:在 React 16.8 版本中引入钩子意味着这些区别不再实用(请参阅 14 和 15 题)。