展现组件(Presentational component)和容器组件(Container component)之间有何不同
展现组件关怀组件看起来是什么。展现专门通过 props 承受数据和回调,并且简直不会有本身的状态,但当展现组件领有本身的状态时,通常也只关怀 UI 状态而不是数据的状态。
容器组件则更关怀组件是如何运作的。容器组件会为展现组件或者其它容器组件提供数据和行为(behavior),它们会调用 Flux actions
,并将其作为回调提供给展现组件。容器组件常常是有状态的,因为它们是(其它组件的)数据源。
高阶组件的利用场景
权限管制
利用高阶组件的 条件渲染 个性能够对页面进行权限管制,权限管制个别分为两个维度:页面级别 和 页面元素级别
// 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 中间件是什么?承受几个参数?柯里化函数两端的参数具体是什么?
Redux 的中间件提供的是位于 action 被发动之后,达到 reducer 之前的扩大点,换而言之,本来 view -→> action -> reducer -> store 的数据流加上中间件后变成了 view -> action -> middleware -> reducer -> store ,在这一环节能够做一些"副作用"的操作,如异步申请、打印日志等。
applyMiddleware源码:
export default function applyMiddleware(...middlewares) { return createStore => (...args) => { // 利用传入的createStore和reducer和创立一个store const store = createStore(...args) let dispatch = () => { throw new Error() } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } // 让每个 middleware 带着 middlewareAPI 这个参数别离执行一遍 const chain = middlewares.map(middleware => middleware(middlewareAPI)) // 接着 compose 将 chain 中的所有匿名函数,组装成一个新的函数,即新的 dispatch dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } }}
从applyMiddleware中能够看出∶
- redux中间件承受一个对象作为参数,对象的参数上有两个字段 dispatch 和 getState,别离代表着 Redux Store 上的两个同名函数。
- 柯里化函数两端一个是 middewares,一个是store.dispatch
React中props.children和React.Children的区别
在React中,当波及组件嵌套,在父组件中应用props.children
把所有子组件显示进去。如下:
function ParentComponent(props){ return ( <div> {props.children} </div> )}
如果想把父组件中的属性传给所有的子组件,须要应用React.Children
办法。
比方,把几个Radio组合起来,合成一个RadioGroup,这就要求所有的Radio具备同样的name属性值。能够这样:把Radio看做子组件,RadioGroup看做父组件,name的属性值在RadioGroup这个父组件中设置。
首先是子组件:
//子组件function RadioOption(props) { return ( <label> <input type="radio" value={props.value} name={props.name} /> {props.label} </label> )}
而后是父组件,不仅须要把它所有的子组件显示进去,还须要为每个子组件赋上name属性和值:
//父组件用,props是指父组件的propsfunction renderChildren(props) { //遍历所有子组件 return React.Children.map(props.children, child => { if (child.type === RadioOption) return React.cloneElement(child, { //把父组件的props.name赋值给每个子组件 name: props.name }) else return child })}//父组件function RadioGroup(props) { return ( <div> {renderChildren(props)} </div> )}function App() { return ( <RadioGroup name="hello"> <RadioOption label="选项一" value="1" /> <RadioOption label="选项二" value="2" /> <RadioOption label="选项三" value="3" /> </RadioGroup> )}export default App;
以上,React.Children.map
让咱们对父组件的所有子组件又更灵便的管制。
如何配置 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属性进行定向。
diff算法如何比拟?
- 只对同级比拟,跨层级的dom不会进行复用
- 不同类型节点生成的dom树不同,此时会间接销毁老节点及子孙节点,并新建节点
- 能够通过key来对元素diff的过程提供复用的线索
- 单节点diff
- 单点diff有如下几种状况:
- key和type雷同示意能够复用节点
- key不同间接标记删除节点,而后新建节点
- key雷同type不同,标记删除该节点和兄弟节点,而后新创建节点
参考 前端进阶面试题具体解答
React 高阶组件、Render props、hooks 有什么区别,为什么要一直迭代
这三者是目前react解决代码复用的次要形式:
- 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 本身不是 React API 的一部分,它是一种基于 React 的组合个性而造成的设计模式。具体而言,高阶组件是参数为组件,返回值为新组件的函数。
- render props是指一种在 React 组件之间应用一个值为函数的 prop 共享代码的简略技术,更具体的说,render prop 是一个用于告知组件须要渲染什么内容的函数 prop。
- 通常,render props 和高阶组件只渲染一个子节点。让 Hook 来服务这个应用场景更加简略。这两种模式仍有用武之地,(例如,一个虚构滚动条组件或者会有一个 renderltem 属性,或是一个可见的容器组件或者会有它本人的 DOM 构造)。但在大部分场景下,Hook 足够了,并且可能帮忙缩小嵌套。
(1)HOC 官网解释∶
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 本身不是 React API 的一部分,它是一种基于 React 的组合个性而造成的设计模式。
简言之,HOC是一种组件的设计模式,HOC承受一个组件和额定的参数(如果须要),返回一个新的组件。HOC 是纯函数,没有副作用。
// 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));
HOC的优缺点∶
- 长处∶ 逻辑服用、不影响被包裹组件的外部逻辑。
- 毛病∶ hoc传递给被包裹组件的props容易和被包裹后的组件重名,进而被笼罩
(2)Render props 官网解释∶
"render prop"是指一种在 React 组件之间应用一个值为函数的 prop 共享代码的简略技术
具备render prop 的组件承受一个返回React元素的函数,将render的渲染逻辑注入到组件外部。在这里,"render"的命名能够是任何其余无效的标识符。
// DataProvider组件外部的渲染逻辑如下class DataProvider extends React.Components { state = { name: 'Tom' } render() { return ( <div> <p>共享数据组件本人外部的渲染逻辑</p> { this.props.render(this.state) } </div> ); }}// 调用形式<DataProvider render={data => ( <h1>Hello {data.name}</h1>)}/>
由此能够看到,render props的优缺点也很显著∶
- 长处:数据共享、代码复用,将组件内的state作为props传递给调用者,将渲染逻辑交给调用者。
- 毛病:无奈在 return 语句外拜访数据、嵌套写法不够优雅
(3)Hooks 官网解释∶
Hook是 React 16.8 的新增个性。它能够让你在不编写 class 的状况下应用 state 以及其余的 React 个性。通过自定义hook,能够复用代码逻辑。
// 自定义一个获取订阅数据的hookfunction useSubscription() { const data = DataSource.getComments(); return [data];}// function CommentList(props) { const {data} = props; const [subData] = useSubscription(); ...}// 应用<CommentList data='hello' />
以上能够看出,hook解决了hoc的prop笼罩的问题,同时应用的形式解决了render props的嵌套天堂的问题。hook的长处如下∶
- 应用直观;
- 解决hoc的prop 重名问题;
- 解决render props 因共享数据 而呈现嵌套天堂的问题;
- 能在return之外应用数据的问题。
须要留神的是:hook只能在组件顶层应用,不可在分支语句中应用。、
React Hooks 解决了哪些问题?
React Hooks 次要解决了以下问题:
(1)在组件之间复用状态逻辑很难
React 没有提供将可复用性行为“附加”到组件的路径(例如,把组件连贯到 store)解决此类问题能够应用 render props 和 高阶组件。然而这类计划须要从新组织组件构造,这可能会很麻烦,并且会使代码难以了解。由 providers,consumers,高阶组件,render props 等其余形象层组成的组件会造成“嵌套天堂”。只管能够在 DevTools 过滤掉它们,但这阐明了一个更深层次的问题:React 须要为共享状态逻辑提供更好的原生路径。
能够应用 Hook 从组件中提取状态逻辑,使得这些逻辑能够独自测试并复用。Hook 使咱们在无需批改组件构造的状况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。
(2)简单组件变得难以了解
在组件中,每个生命周期经常蕴含一些不相干的逻辑。例如,组件经常在 componentDidMount 和 componentDidUpdate 中获取数据。然而,同一个 componentDidMount 中可能也蕴含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中革除。互相关联且须要对照批改的代码被进行了拆分,而齐全不相干的代码却在同一个办法中组合在一起。如此很容易产生 bug,并且导致逻辑不统一。
在少数状况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了肯定挑战。同时,这也是很多人将 React 与状态治理库联合应用的起因之一。然而,这往往会引入了很多抽象概念,须要你在不同的文件之间来回切换,使得复用变得更加艰难。
为了解决这个问题,Hook 将组件中互相关联的局部拆分成更小的函数(比方设置订阅或申请数据),而并非强制依照生命周期划分。你还能够应用 reducer 来治理组件的外部状态,使其更加可预测。
(3)难以了解的 class
除了代码复用和代码治理会遇到困难外,class 是学习 React 的一大屏障。咱们必须去了解 JavaScript 中 this 的工作形式,这与其余语言存在微小差别。还不能遗记绑定事件处理器。没有稳固的语法提案,这些代码十分冗余。大家能够很好地了解 props,state 和自顶向下的数据流,但对 class 却束手无策。即使在有教训的 React 开发者之间,对于函数组件与 class 组件的差别也存在一致,甚至还要辨别两种组件的应用场景。
为了解决这些问题,Hook 使你在非 class 的状况下能够应用更多的 React 个性。 从概念上讲,React 组件始终更像是函数。而 Hook 则拥抱了函数,同时也没有就义 React 的精力准则。Hook 提供了问题的解决方案,无需学习简单的函数式或响应式编程技术
Redux 和 Vuex 有什么区别,它们的独特思维
(1)Redux 和 Vuex区别
- Vuex改良了Redux中的Action和Reducer函数,以mutations变动函数取代Reducer,无需switch,只需在对应的mutation函数里扭转state值即可
- Vuex因为Vue主动从新渲染的个性,无需订阅从新渲染函数,只有生成新的State即可
- Vuex数据流的程序是∶View调用store.commit提交对应的申请到Store中对应的mutation函数->store扭转(vue检测到数据变动主动渲染)
艰深点了解就是,vuex 弱化 dispatch,通过commit进行 store状态的一次更变;勾销了action概念,不用传入特定的 action模式进行指定变更;弱化reducer,基于commit参数间接对数据进行转变,使得框架更加繁难;
(2)独特思维
- 单—的数据源
- 变动能够预测
实质上∶ redux与vuex都是对mvvm思维的服务,将数据从视图中抽离的一种计划。
React中的props为什么是只读的?
this.props
是组件之间沟通的一个接口,原则上来讲,它只能从父组件流向子组件。React具备浓厚的函数式编程的思维。
提到函数式编程就要提一个概念:纯函数。它有几个特点:
- 给定雷同的输出,总是返回雷同的输入。
- 过程没有副作用。
- 不依赖内部状态。
this.props
就是吸取了纯函数的思维。props的不能够变性就保障的雷同的输出,页面显示的内容是一样的,并且不会产生副作用
辨别状态和 props
条件 | State | Props |
---|---|---|
1. 从父组件中接管初始值 | Yes | Yes |
2. 父组件能够扭转值 | No | Yes |
3. 在组件中设置默认值 | Yes | Yes |
4. 在组件的外部变动 | Yes | No |
5. 设置子组件的初始值 | Yes | Yes |
6. 在子组件的外部更改 | No | Yes |
何为 action
Actions 是一个纯 javascript 对象,它们必须有一个 type 属性表明正在执行的 action 的类型。本质上,action 是将数据从应用程序发送到 store 的有效载荷。
对虚构 DOM 的了解?虚构 DOM 次要做了什么?虚构 DOM 自身是什么?
从实质上来说,Virtual Dom是一个JavaScript对象,通过对象的形式来示意DOM构造。将页面的状态形象为JS对象的模式,配合不同的渲染工具,使跨平台渲染成为可能。通过事务处理机制,将屡次DOM批改的后果一次性的更新到页面上,从而无效的缩小页面渲染的次数,缩小批改DOM的重绘重排次数,进步渲染性能。
虚构DOM是对DOM的形象,这个对象是更加轻量级的对DOM的形容。它设计的最后目标,就是更好的跨平台,比方node.js就没有DOM,如果想实现SSR,那么一个形式就是借助虚构dom,因为虚构dom自身是js对象。 在代码渲染到页面之前,vue或者react会把代码转换成一个对象(虚构DOM)。以对象的模式来形容实在dom构造,最终渲染到页面。在每次数据发生变化前,虚构dom都会缓存一份,变动之时,当初的虚构dom会与缓存的虚构dom进行比拟。在vue或者react外部封装了diff算法,通过这个算法来进行比拟,渲染时批改扭转的变动,原先没有产生扭转的通过原先的数据进行渲染。
另外古代前端框架的一个根本要求就是毋庸手动操作DOM,一方面是因为手动操作DOM无奈保障程序性能,多人合作的我的项目中如果review不严格,可能会有开发者写出性能较低的代码,另一方面更重要的是省略手动DOM操作能够大大提高开发效率。
为什么要用 Virtual DOM:
(1)保障性能上限,在不进行手动优化的状况下,提供过得去的性能
上面比照一下批改DOM时实在DOM操作和Virtual DOM的过程,来看一下它们重排重绘的性能耗费∶
- 实在DOM∶ 生成HTML字符串+ 重建所有的DOM元素
- Virtual DOM∶ 生成vNode+ DOMDiff+必要的DOM更新
Virtual DOM的更新DOM的筹备工作消耗更多的工夫,也就是JS层面,相比于更多的DOM操作它的生产是极其便宜的。尤雨溪在社区论坛中说道∶ 框架给你的保障是,你不须要手动优化的状况下,我仍然能够给你提供过得去的性能。 (2)跨平台 Virtual DOM实质上是JavaScript的对象,它能够很不便的跨平台操作,比方服务端渲染、uniapp等。
对有状态组件和无状态组件的了解及应用场景
(1)有状态组件
特点:
- 是类组件
- 有继承
- 能够应用this
- 能够应用react的生命周期
- 应用较多,容易频繁触发生命周期钩子函数,影响性能
- 外部应用 state,保护本身状态的变动,有状态组件依据内部组件传入的 props 和本身的 state进行渲染。
应用场景:
- 须要应用到状态的。
- 须要应用状态操作组件的(无状态组件的也能够实现新版本react hooks也可实现)
总结: 类组件能够保护本身的状态变量,即组件的 state ,类组件还有不同的生命周期办法,能够让开发者可能在组件的不同阶段(挂载、更新、卸载),对组件做更多的管制。类组件则既能够充当无状态组件,也能够充当有状态组件。当一个类组件不须要治理本身状态时,也可称为无状态组件。
(2)无状态组件 特点:
- 不依赖本身的状态state
- 能够是类组件或者函数组件。
- 能够完全避免应用 this 关键字。(因为应用的是箭头函数事件无需绑定)
- 有更高的性能。当不须要应用生命周期钩子时,应该首先应用无状态函数组件
- 组件外部不保护 state ,只依据内部组件传入的 props 进行渲染的组件,当 props 扭转时,组件从新渲染。
应用场景:
- 组件不须要治理 state,纯展现
长处:
- 简化代码、专一于 render
- 组件不须要被实例化,无生命周期,晋升性能。 输入(渲染)只取决于输出(属性),无副作用
- 视图和数据的解耦拆散
毛病:
- 无奈应用 ref
- 无生命周期办法
- 无法控制组件的重渲染,因为无奈应用shouldComponentUpdate 办法,当组件承受到新的属性时则会重渲染
总结: 组件外部状态且与内部无关的组件,能够思考用状态组件,这样状态树就不会过于简单,易于了解和治理。当一个组件不须要治理本身状态时,也就是无状态组件,应该优先设计为函数组件。比方自定义的 <Button/>
、 <Input />
等组件。
对React-Fiber的了解,它解决了什么问题?
React V15 在渲染时,会递归比对 VirtualDOM 树,找出须要变动的节点,而后同步更新它们, 零打碎敲。这个过程期间, React 会占据浏览器资源,这会导致用户触发的事件得不到响应,并且会导致掉帧,导致用户感觉到卡顿。
为了给用户制作一种利用很快的“假象”,不能让一个工作长期霸占着资源。 能够将浏览器的渲染、布局、绘制、资源加载(例如 HTML 解析)、事件响应、脚本执行视作操作系统的“过程”,须要通过某些调度策略正当地调配 CPU 资源,从而进步浏览器的用户响应速率, 同时兼顾工作执行效率。
所以 React 通过Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了能够让浏览器及时地响应用户的交互,还有其余益处:
- 分批延时对DOM进行操作,防止一次性操作大量 DOM 节点,能够失去更好的用户体验;
- 给浏览器一点喘息的机会,它会对代码进行编译优化(JIT)及进行热代码优化,或者对 reflow 进行修改。
核心思想: Fiber 也称协程或者纤程。它和线程并不一样,协程自身是没有并发或者并行能力的(须要配合线程),它只是一种管制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其余的操作。渲染的过程能够被中断,能够将控制权交回浏览器,让位给高优先级的工作,浏览器闲暇后再复原渲染。
对于store的了解
Store 就是把它们分割到一起的对象。Store 有以下职责:
- 维持利用的 state;
- 提供 getState() 办法获取 state;
- 提供 dispatch(action) 办法更新 state;
- 通过 subscribe(listener)注册监听器;
- 通过 subscribe(listener)返回的函数登记监听器
形容 Flux 与 MVC?
传统的 MVC 模式在拆散数据(Model)、UI(View和逻辑(Controller)方面工作得很好,然而 MVC 架构常常遇到两个次要问题:
数据流不够清晰:跨视图产生的级联更新经常会导致凌乱的事件网络,难于调试。
不足数据完整性:模型数据能够在任何中央产生渐变,从而在整个UI中产生不可预测的后果。
应用 Flux 模式的简单用户界面不再蒙受级联更新,任何给定的React 组件都可能依据 store
提供的数据重建其状态。Flux 模式还通过限度对共享数据的间接拜访来增强数据完整性。
在 React 中,何为 state
State 和 props 相似,但它是公有的,并且齐全由组件本身管制。State 实质上是一个持有数据,并决定组件如何渲染的对象。
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-Router的实现原理是什么?
客户端路由实现的思维:
基于 hash 的路由:通过监听
hashchange
事件,感知 hash 的变动- 扭转 hash 能够间接通过 location.hash=xxx
基于 H5 history 路由:
- 扭转 url 能够通过 history.pushState 和 resplaceState 等,会将URL压入堆栈,同时可能利用
history.go()
等 API - 监听 url 的变动能够通过自定义事件触发实现
- 扭转 url 能够通过 history.pushState 和 resplaceState 等,会将URL压入堆栈,同时可能利用
react-router 实现的思维:
- 基于
history
库来实现上述不同的客户端路由实现思维,并且可能保留历史记录等,磨平浏览器差别,下层无感知 - 通过保护的列表,在每次 URL 发生变化的回收,通过配置的 路由门路,匹配到对应的 Component,并且 render