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); // 1console.log(two); // 2console.log(three); // 3
对象的解构赋值
const user = { id: 888, name: "xiaoxin"};const { id, name } = user;console.log(id); // 888console.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题)。