(在构造函数中)调用 super(props) 的目的是什么
在 super()
被调用之前,子类是不能使用 this
的,在 ES2015 中,子类必须在 constructor
中调用 super()
。传递 props
给 super()
的原因则是便于(在子类中)能在 constructor
访问 this.props
。
为什么不直接更新 state
呢 ?
如果试图直接更新 state
,则不会重新渲染组件。
// 错误
This.state.message = 'Hello world';
需要使用setState()
方法来更新 state
。它调度对组件state
对象的更新。当state
改变时,组件通过重新渲染来响应:
// 正确做法
This.setState({message: ‘Hello World’});
react 的渲染过程中,兄弟节点之间是怎么处理的?也就是key值不一样的时候
通常我们输出节点的时候都是map一个数组然后返回一个
ReactNode
,为了方便react
内部进行优化,我们必须给每一个reactNode
添加key
,这个key prop
在设计值处不是给开发者用的,而是给react用的,大概的作用就是给每一个reactNode
添加一个身份标识,方便react进行识别,在重渲染过程中,如果key一样,若组件属性有所变化,则react
只更新组件对应的属性;没有变化则不更新,如果key不一样,则react先销毁该组件,然后重新创建该组件
vue 或者react 优化整体优化
- 虚拟dom
为什么虚拟 dom 会提高性能?(必考)
虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异把 2 所记录的差异应用到步骤 1 所构建的真正的 DOM 树上,视图就更新了。
这段代码有什么问题?
代码语言:javascript复制class App extends Component {
constructor(props) {
super(props);
this.state = {
username: "有课前端网",
msg: " ",
};
}
render() {
return <div> {this.state.msg}</div>;
}
componentDidMount() {
this.setState((oldState, props) => {
return {
msg: oldState.username " - " props.intro,
};
});
}
}
render ( < App intro=" 前端技术专业学习平台"></App>,ickt )
在页面中正常输出“有课前端网-前端技术专业学习平台”。但是这种写法很少使用,并不是常用的写法。React允许对 setState方法传递一个函数,它接收到先前的状态和属性数据并返回一个需要修改的状态对象,正如我们在上面所做的那样。它不但没有问题,而且如果根据以前的状态( state)以及属性来修改当前状态,推荐使用这种写法。
什么情况下使用异步组件
- 提高页面加载速度,使用
reloadable
把各个页面分别单独打包,按需加载
React中keys的作用是什么?
代码语言:javascript复制render () {
return (
<ul>
{this.state.todoItems.map(({item,i}) => {
return <li key={i}>{item}</li>
})}
</ul>
)
}
在React Diff算法中React会借助元素的Key值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。
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 只会为其保留最后一次的更新)。
什么是JSX
jsx 是 JavaScriptXML的简写,是react使用的一种文件,它利用 JavaScript 的表现力和类似 HTML 的模板语法,这使得 HTML 文件非常容易理解。此文件能使应用非常可靠,并能够提高其性能
jsx的语法规则
- 定义虚拟DOM的时候 不需要写引号
- 标签中要混入js表达式的时候需要用 {}
- 在jsx中写标签的类名的时候 用className 代替class
- 内联样式的时候 ,需要style={{key:value}}
- 标签必须要闭合
- 标签首字母的约定
若为小写字母,则将jsx转换为html中同名元素,若html中无该标签明对应的同名元素 则报错
若为大写字母,react就去渲染对应的组件,若没有定义组件 则报错
- 当根据数据遍历生成的标签,一定要给标签设置单独的key 否则会报错
hooks 为什么不能放在条件判断里
以 setState 为例,在 react 内部,每个组件(Fiber)的 hooks 都是以链表的形式存在 memoizeState 属性中
update 阶段,每次调用 setState,链表就会执行 next 向后移动一步。如果将 setState 写在条件判断中,假设条件判断不成立,没有执行里面的 setState 方法,会导致接下来所有的 setState 的取值出现偏移,从而导致异常发生。
何为 Children
在JSX表达式中,一个开始标签(比如<a>
)和一个关闭标签(比如</a>
)之间的内容会作为一个特殊的属性props.children
被自动传递给包含着它的组件。
这个属性有许多可用的方法,包括 React.Children.map
,React.Children.forEach
, React.Children.count
, React.Children.only
,React.Children.toArray
。
哪些方法会触发 React 重新渲染?重新渲染 render 会做些什么?
(1)哪些方法会触发 react 重新渲染?
- setState()方法被调用
setState 是 React 中最常用的命令,通常情况下,执行 setState 会触发 render。但是这里有个点值得关注,执行 setState 的时候不一定会重新渲染。当 setState 传入 null 时,并不会触发 render。
代码语言:javascript复制class App extends React.Component {
state = {
a: 1
};
render() {
console.log("render");
return (
<React.Fragement>
<p>{this.state.a}</p>
<button
onClick={() => { this.setState({ a: 1 }); // 这里并没有改变 a 的值 }} > Click me </button>
<button onClick={() => this.setState(null)}>setState null</button>
<Child />
</React.Fragement>
);
}
}
- 父组件重新渲染
只要父组件重新渲染了,即使传入子组件的 props 未发生变化,那么子组件也会重新渲染,进而触发 render
(2)重新渲染 render 会做些什么?
- 会对新旧 VNode 进行对比,也就是我们所说的Diff算法。
- 对新旧两棵树进行一个深度优先遍历,这样每一个节点都会一个标记,在到深度遍历的时候,每遍历到一和个节点,就把该节点和新的节点树进行对比,如果有差异就放到一个对象里面
- 遍历差异对象,根据差异的类型,根据对应对规则更新VNode
React 的处理 render 的基本思维模式是每次一有变动就会去重新渲染整个应用。在 Virtual DOM 没有出现之前,最简单的方法就是直接调用 innerHTML。Virtual DOM厉害的地方并不是说它比直接操作 DOM 快,而是说不管数据怎么变,都会尽量以最小的代价去更新 DOM。React 将 render 函数返回的虚拟 DOM 树与老的进行比较,从而确定 DOM 要不要更新、怎么更新。当 DOM 树很大时,遍历两棵树进行各种比对还是相当耗性能的,特别是在顶层 setState 一个微小的修改,默认会去遍历整棵树。尽管 React 使用高度优化的 Diff 算法,但是这个过程仍然会损耗性能.
React Hooks 和生命周期的关系?
函数组件 的本质是函数,没有 state 的概念的,因此不存在生命周期一说,仅仅是一个 render 函数而已。
但是引入 Hooks 之后就变得不同了,它能让组件在不使用 class 的情况下拥有 state,所以就有了生命周期的概念,所谓的生命周期其实就是 useState
、 useEffect()
和 useLayoutEffect()
。
即:Hooks 组件(使用了Hooks的函数组件)有生命周期,而函数组件(未使用Hooks的函数组件)是没有生命周期的。
下面是具体的 class 与 Hooks 的生命周期对应关系:
constructor
:函数组件不需要构造函数,可以通过调用**useState 来初始化 state**
。如果计算的代价比较昂贵,也可以传一个函数给useState
。
const [num, UpdateNum] = useState(0)
getDerivedStateFromProps
:一般情况下,我们不需要使用它,可以在渲染过程中更新 state,以达到实现getDerivedStateFromProps
的目的。
function ScrollView({row}) {
let [isScrollingDown, setIsScrollingDown] = useState(false);
let [prevRow, setPrevRow] = useState(null);
if (row !== prevRow) {
// Row 自上次渲染以来发生过改变。更新 isScrollingDown。
setIsScrollingDown(prevRow !== null && row > prevRow);
setPrevRow(row);
}
return `Scrolling down: ${isScrollingDown}`;
}
React 会立即退出第一次渲染并用更新后的 state 重新运行组件以避免耗费太多性能。
shouldComponentUpdate
:可以用**React.memo**
包裹一个组件来对它的props
进行浅比较
const Button = React.memo((props) => { // 具体的组件});
注意:**React.memo 等效于 **``**PureComponent**
,它只浅比较 props。这里也可以使用 useMemo
优化每一个节点。
render
:这是函数组件体本身。componentDidMount
,componentDidUpdate
:useLayoutEffect
与它们两的调用阶段是一样的。但是,我们推荐你一开始先用 useEffect,只有当它出问题的时候再尝试使用useLayoutEffect
。useEffect
可以表达所有这些的组合。
// componentDidMount
useEffect(()=>{
// 需要在 componentDidMount 执行的内容
}, [])
useEffect(() => {
// 在 componentDidMount,以及 count 更改时 componentDidUpdate 执行的内容
document.title = `You clicked ${count} times`;
return () => {
// 需要在 count 更改时 componentDidUpdate(先于 document.title = ... 执行,遵守先清理后更新)
// 以及 componentWillUnmount 执行的内容
} // 当函数中 Cleanup 函数会按照在代码中定义的顺序先后执行,与函数本身的特性无关
}, [count]); // 仅在 count 更改时更新
请记得 React 会等待浏览器完成画面渲染之后才会延迟调用 ,因此会使得额外操作很方便
componentWillUnmount
:相当于useEffect
里面返回的cleanup
函数
// componentDidMount/componentWillUnmount
useEffect(()=>{
// 需要在 componentDidMount 执行的内容
return function cleanup() {
// 需要在 componentWillUnmount 执行的内容
}
}, [])
componentDidCatch
andgetDerivedStateFromError
:目前还没有这些方法的 Hook 等价写法,但很快会加上。
class 组件 | Hooks 组件 |
---|---|
constructor | useState |
getDerivedStateFromProps | useState 里面 update 函数 |
shouldComponentUpdate | useMemo |
render | 函数本身 |
componentDidMount | useEffect |
componentDidUpdate | useEffect |
componentWillUnmount | useEffect 里面返回的函数 |
componentDidCatch | 无 |
getDerivedStateFromError | 无 |
在哪个生命周期中你会发出Ajax请求?为什么?
Ajax请求应该写在组件创建期的第五个阶段,即 componentDidMount生命周期方法中。原因如下。
在创建期的其他阶段,组件尚未渲染完成。而在存在期的5个阶段,又不能确保生命周期方法一定会执行(如通过 shouldComponentUpdate方法优化更新等)。在销毀期,组件即将被销毁,请求数据变得无意义。因此在这些阶段发岀Ajax请求显然不是最好的选择。
在组件尚未挂载之前,Ajax请求将无法执行完毕,如果此时发出请求,将意味着在组件挂载之前更新状态(如执行 setState),这通常是不起作用的。
在 componentDidMount方法中,执行Ajax即可保证组件已经挂载,并且能够正常更新组件。
setState 是同步的还是异步的
有时表现出同步,有时表现出异步
- setState 只有在 React 自身的合成事件和钩子函数中是异步的,在原生事件和 setTimeout 中都是同步的
- setState 的异步并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的异步。当然可以通过 setState 的第二个参数中的 callback 拿到更新后的结果
- setState 的批量更新优化也是建立在异步(合成事件、钩子函数)之上的,在原生事件和 setTimeout 中不会批量更新,在异步中如果对同一个值进行多次 setState,setState 的批量更新策略会对其进行覆盖,去最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新
- 合成事件中是异步
- 钩子函数中的是异步
- 原生事件中是同步
- setTimeout中是同步
redux异步中间件之间的优劣?
redux-thunk优点:
- 体积⼩:redux-thunk的实现⽅式很简单,只有不到20⾏代码;
- 使⽤简单:redux-thunk没有引⼊像redux-saga或者redux-observable额外的范式,上⼿简单。
redux-thunk缺陷:
- 样板代码过多:与redux本身⼀样,通常⼀个请求需要⼤量的代码,⽽且很多都是重复性质的;
- 耦合严重:异步操作与redux的action偶合在⼀起,不⽅便管理;
- 功能孱弱:有⼀些实际开发中常⽤的功能需要⾃⼰进⾏封装。
redux-saga优点:
- 异步解耦:异步操作被被转移到单独saga.js中,不再是掺杂在action.js或component.js中;
- action摆脱thunk function: dispatch的参数依然是⼀个纯粹的 action (FSA),⽽不是充满 “⿊魔法” thunk function;
- 异常处理:受益于 generator function 的saga实现,代码异常/请求失败都可以直接通过try/catch语法直接捕获处理;
- 功能强⼤:redux-saga提供了⼤量的Saga辅助函数和Effect创建器供开发者使⽤,开发者⽆须封装或者简单封装即可使⽤;
- 灵活:redux-saga可以将多个Saga可以串⾏/并⾏组合起来,形成⼀个⾮常实⽤的异步flow;
- 易测试,提供了各种case的测试⽅案,包括mock task,分⽀覆盖等等。
redux-saga缺陷:
- 额外的学习成本:redux-saga不仅在使⽤难以理解的generator function,⽽且有数⼗个API,学习成本远超reduxthunk,最重要的是你的额外学习成本是只服务于这个库的,与redux-observable不同,redux-observable虽然也有额外学习成本但是背后是rxjs和⼀整套思想;
- 体积庞⼤:体积略⼤,代码近2000⾏,min版25KB左右;
- 功能过剩:实际上并发控制等功能很难⽤到,但是我们依然需要引⼊这些代码;
- ts⽀持不友好:yield⽆法返回TS类型。
redux-observable优点:
- 功能最强:由于背靠rxjs这个强⼤的响应式编程的库,借助rxjs的操作符,你可以⼏乎做任何你能想到的异步处理;
- 背靠rxjs:由于有rxjs的加持,如果你已经学习了rxjs,redux-observable的学习成本并不⾼,⽽且随着rxjs的升级reduxobservable也会变得更强⼤。
redux-observable缺陷:
- 学习成本奇⾼:如果你不会rxjs,则需要额外学习两个复杂的库;
- 社区⼀般:redux-observable的下载量只有redux-saga的1/5,社区也不够活跃,在复杂异步流中间件这个层⾯reduxsaga仍处于领导地位。
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 来控制路由跳转的:
代码语言:javascript复制<BrowserRouter
basename={string}
forceRefresh={bool}
getUserConfirmation={func}
keyLength={number}
/>
其中的属性如下:
- basename 所有路由的基准 URL。basename 的正确格式是前面有一个前导斜杠,但不能有尾部斜杠;
<BrowserRouter basename="/calendar">
<Link to="/today" />
</BrowserRouter>
等同于
代码语言:javascript复制<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 属性来控制路由跳转的:
代码语言:javascript复制<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。
指出(组件)生命周期方法的不同
componentWillMount
-- 多用于根组件中的应用程序配置componentDidMount
-- 在这可以完成所有没有 DOM 就不能做的所有配置,并开始获取所有你需要的数据;如果需要设置事件监听,也可以在这完成componentWillReceiveProps
-- 这个周期函数作用于特定的 prop 改变导致的 state 转换shouldComponentUpdate
-- 如果你担心组件过度渲染,shouldComponentUpdate
是一个改善性能的地方,因为如果组件接收了新的prop
, 它可以阻止(组件)重新渲染。shouldComponentUpdate 应该返回一个布尔值来决定组件是否要重新渲染componentWillUpdate
-- 很少使用。它可以用于代替组件的componentWillReceiveProps
和shouldComponentUpdate
(但不能访问之前的 props)componentDidUpdate
-- 常用于更新 DOM,响应 prop 或 state 的改变componentWillUnmount
-- 在这你可以取消网络请求,或者移除所有与组件相关的事件监听器
React与Angular有何不同?
主题 | React | Angular |
---|---|---|
1. 体系结构 | 只有 MVC 中的 View | 完整的 MVC |
2. 渲染 | 可以在服务器端渲染 | 客户端渲染 |
3. DOM | 使用 virtual DOM | 使用 real DOM |
4. 数据绑定 | 单向数据绑定 | 双向数据绑定 |
5. 调试 | 编译时调试 | 运行时调试 |
6. 作者 |
React中的props为什么是只读的?
this.props
是组件之间沟通的一个接口,原则上来讲,它只能从父组件流向子组件。React具有浓重的函数式编程的思想。
提到函数式编程就要提一个概念:纯函数。它有几个特点:
- 给定相同的输入,总是返回相同的输出。
- 过程没有副作用。
- 不依赖外部状态。
this.props
就是汲取了纯函数的思想。props的不可以变性就保证的相同的输入,页面显示的内容是一样的,并且不会产生副作用