共计 18460 个字符,预计需要花费 47 分钟才能阅读完成。
redux 与 mobx 的区别?
两者对⽐:
- redux 将数据保留在单⼀的 store 中,mobx 将数据保留在扩散的多个 store 中
- redux 使⽤ plain object 保留数据,须要⼿动解决变动后的操作;mobx 适⽤ observable 保留数据,数据变动后⾃动解决响应的操作
- redux 使⽤不可变状态,这意味着状态是只读的,不能间接去批改它,⽽是应该返回⼀个新的状态,同时使⽤纯函数;mobx 中的状态是可变的,能够间接对其进⾏批改
mobx 相对来说⽐较简略,在其中有很多的形象,mobx 更多的使⽤⾯向对象的编程思维;redux 会⽐较简单,因为其中的函数式编程思维把握起来不是那么容易,同时须要借助⼀系列的中间件来解决异步和副作⽤
- mobx 中有更多的形象和封装,调试会⽐较艰难,同时后果也难以预测;⽽ redux 提供可能进⾏工夫回溯的开发⼯具,同时其纯函数以及更少的形象,让调试变得更加的容易
场景辨析:
- 基于以上区别,咱们能够简略得剖析⼀下两者的不同使⽤场景。
- mobx 更适宜数据不简单的应⽤:mobx 难以调试,很多状态⽆法回溯,⾯对复杂度⾼的应⽤时,往往⼒不从⼼。
- redux 适宜有回溯需要的应⽤:⽐如⼀个画板应⽤、⼀个表格应⽤,很多时候须要撤销、重做等操作,因为 redux 不可变的个性,人造⽀持这些操作。
- mobx 适宜短平快的项⽬:mobx 上⼿简略,样板代码少,能够很⼤水平上提⾼开发效率。
- 当然 mobx 和 redux 也并不⼀定是⾮此即彼的关系,你也能够在项⽬中⽤ redux 作为全局状态治理,⽤ mobx 作为组件部分状态管理器来⽤。
Redux 怎么实现属性传递,介绍下原理
react-redux 数据传输∶ view–>action–>reducer–>store–>view。看下点击事件的数据是如何通过 redux 传到 view 上:
- view 上的 AddClick 事件通过 mapDispatchToProps 把数据传到 action —> click:()=>dispatch(ADD)
- action 的 ADD 传到 reducer 上
- reducer 传到 store 上 const store = createStore(reducer);
- store 再通过 mapStateToProps 映射穿到 view 上 text:State.text
代码示例∶
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore} from 'redux';
import {Provider, connect} from 'react-redux';
class App extends React.Component{render(){let { text, click, clickR} = this.props;
return(
<div>
<div> 数据: 已有人{text}</div>
<div onClick={click}> 加人 </div>
<div onClick={clickR}> 减人 </div>
</div>
)
}
}
const initialState = {text:5}
const reducer = function(state,action){switch(action.type){
case 'ADD':
return {text:state.text+1}
case 'REMOVE':
return {text:state.text-1}
default:
return initialState;
}
}
let ADD = {type:'ADD'}
let Remove = {type:'REMOVE'}
const store = createStore(reducer);
let mapStateToProps = function (state){
return{text:state.text}
}
let mapDispatchToProps = function(dispatch){
return{click:()=>dispatch(ADD),
clickR:()=>dispatch(Remove)
}
}
const App1 = connect(mapStateToProps,mapDispatchToProps)(App);
ReactDOM.render(<Provider store = {store}>
<App1></App1>
</Provider>,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 层级过深的问题
- 应用 Context API:提供一种组件之间的状态共享,而不用通过显式组件树逐层传递 props;
- 应用 Redux 等状态库。
React 事件机制
<div onClick={this.handleClick.bind(this)}> 点我 </div>
React 并不是将 click 事件绑定到了 div 的实在 DOM 上,而是在 document 处监听了所有的事件,当事件产生并且冒泡到 document 处的时候,React 将事件内容封装并交由真正的处理函数运行。这样的形式不仅仅缩小了内存的耗费,还能在组件挂在销毁时对立订阅和移除事件。
除此之外,冒泡到 document 上的事件也不是原生的浏览器事件,而是由 react 本人实现的合成事件(SyntheticEvent)。因而如果不想要是事件冒泡的话应该调用 event.preventDefault()办法,而不是调用 event.stopProppagation()办法。JSX 上写的事件并没有绑定在对应的实在 DOM 上,而是通过事件代理的形式,将所有的事件都对立绑定在了 document
上。这样的形式不仅缩小了内存耗费,还能在组件挂载销毁时对立订阅和移除事件。
另外冒泡到 document
上的事件也不是原生浏览器事件,而是 React 本人实现的合成事件(SyntheticEvent)。因而咱们如果不想要事件冒泡的话,调用 event.stopPropagation
是有效的,而应该调用 event.preventDefault
。
实现合成事件的目标如下:
- 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;
- 对于原生浏览器事件来说,浏览器会给监听器创立一个事件对象。如果你有很多的事件监听,那么就须要调配很多的事件对象,造成高额的内存调配问题。然而对于合成事件来说,有一个事件池专门来治理它们的创立和销毁,当事件须要被应用时,就会从池子中复用对象,事件回调完结后,就会销毁事件对象上的属性,从而便于下次复用事件对象。
如何配置 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 属性进行定向。
参考 前端进阶面试题具体解答
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,能够复用代码逻辑。
// 自定义一个获取订阅数据的 hook
function 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 只能在组件顶层应用,不可在分支语句中应用。、
什么是高阶组件
高阶组件不是组件,是 加强函数,能够输出一个元组件,返回出一个新的加强组件
- 属性代理 (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();
}
}
}
非嵌套关系组件的通信形式?
即没有任何蕴含关系的组件,包含兄弟组件以及不在同一个父级中的非兄弟组件。
- 能够应用自定义事件通信(公布订阅模式)
- 能够通过 redux 等进行全局状态治理
- 如果是兄弟组件通信,能够找到这两个兄弟节点独特的父节点, 联合父子间通信形式进行通信。
组件通信的形式有哪些
- ⽗组件向⼦组件通信: ⽗组件能够向⼦组件通过传 props 的⽅式,向⼦组件进⾏通信
- ⼦组件向⽗组件通信: props+ 回调的⽅式,⽗组件向⼦组件传递 props 进⾏通信,此 props 为作⽤域为⽗组件⾃身的函 数,⼦组件调⽤该函数,将⼦组件想要传递的信息,作为参数,传递到⽗组件的作⽤域中
- 兄弟组件通信: 找到这两个兄弟节点独特的⽗节点, 联合上⾯两种⽅式由⽗节点转发信息进⾏通信
- 跨层级通信: Context 设计⽬的是为了共享那些对于⼀个组件树⽽⾔是“全局”的数据,例如以后认证的⽤户、主题或⾸选语⾔,对于逾越多层的全局数据通过 Context 通信再适宜不过
- 公布订阅模式: 发布者公布事件,订阅者监听事件并做出反馈, 咱们能够通过引⼊ event 模块进⾏通信
- 全局状态治理⼯具: 借助 Redux 或者 Mobx 等全局状态治理⼯具进⾏通信, 这种⼯具会保护⼀个全局状态中⼼ Store, 并依据不同的事件产⽣新的状态
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 代理原生事件为什么?
通过冒泡实现,为了对立治理,对更多浏览器有兼容成果
合成事件原理
如果 react 事件绑定在了实在 DOM 节点上,一个节点共事有多个事件时,页面的响应和内存的占用会受到很大的影响。因而 SyntheticEvent 作为中间层呈现了。
事件没有在指标对象上绑定,而是在 document 上监听所反对的所有事件,当事件产生并冒泡至 document 时,react 将事件内容封装并叫由真正的处理函数运行。
版权申明:本文为 CSDN 博主「jiuwanli666」的原创文章,遵循 CC 4.0 BY-SA 版权协定,转载请附上原文出处链接及本申明。
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 封装的状态管理工具。
setState 之后 产生了什么?
- (1)代码中调用 setState 函数之后,React 会将传入的参数对象与组件以后的状态合并,而后触发所谓的和谐过程(Reconciliation)。
- (2)通过和谐过程,React 会以绝对高效的形式依据新的状态构建 React 元素树并且着手从新渲染整个 UI 界面;
- (3)在 React 失去元素树之后,React 会主动计算出新的树与老树的节点差别,而后依据差别对界面进行最小化重渲染;
- (4)在差别计算算法中,React 可能绝对准确地晓得哪些地位产生了扭转以及应该如何扭转,这就保障了按需更新,而不是全副从新渲染。
setState 的调用会引起 React 的更新生命周期的 4 个函数执行。
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
React 的生命周期有哪些?
React 通常将组件生命周期分为三个阶段:
- 装载阶段(Mount),组件第一次在 DOM 树中被渲染的过程;
- 更新过程(Update),组件状态发生变化,从新更新渲染的过程;
- 卸载过程(Unmount),组件从 DOM 树中被移除的过程;
1)组件挂载阶段
挂载阶段组件被创立,而后组件实例插入到 DOM 中,实现组件的第一次渲染,该过程只会产生一次,在此阶段会顺次调用以下这些办法:
- constructor
- getDerivedStateFromProps
- render
- componentDidMount
(1)constructor
组件的构造函数,第一个被执行,若没有显式定义它,会有一个默认的构造函数,然而若显式定义了构造函数,咱们必须在构造函数中执行 super(props)
,否则无奈在构造函数中拿到 this。
如果不初始化 state 或不进行办法绑定,则不须要为 React 组件实现构造函数Constructor。
constructor 中通常只做两件事:
- 初始化组件的 state
- 给事件处理办法绑定 this
constructor(props) {super(props);
// 不要在构造函数中调用 setState,能够间接给 state 设置初始值
this.state = {counter: 0}
this.handleClick = this.handleClick.bind(this)
}
(2)getDerivedStateFromProps
static getDerivedStateFromProps(props, state)
这是个静态方法,所以不能在这个函数里应用 this
,有两个参数 props
和 state
,别离指接管到的新参数和以后组件的 state
对象,这个函数会返回一个对象用来更新以后的 state
对象,如果不须要更新能够返回 null
。
该函数会在装载时,接管到新的 props
或者调用了 setState
和 forceUpdate
时被调用。如当接管到新的属性想批改 state
,就能够应用。
// 当 props.counter 变动时,赋值给 state
class App extends React.Component {constructor(props) {super(props)
this.state = {counter: 0}
}
static getDerivedStateFromProps(props, state) {if (props.counter !== state.counter) {
return {counter: props.counter}
}
return null
}
handleClick = () => {
this.setState({counter: this.state.counter + 1})
}
render() {
return (
<div>
<h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
</div>
)
}
}
当初能够显式传入 counter
,然而这里有个问题,如果想要通过点击实现 state.counter
的减少,但这时会发现值不会产生任何变动,始终放弃 props
传进来的值。这是因为在 React 16.4^ 的版本中 setState
和 forceUpdate
也会触发这个生命周期,所以当组件外部 state
变动后,就会从新走这个办法,同时会把 state
值赋值为 props
的值。因而须要多加一个字段来记录之前的 props
值,这样就会解决上述问题。具体如下:
// 这里只列出须要变动的中央
class App extends React.Component {constructor(props) {super(props)
this.state = {
// 减少一个 preCounter 来记录之前的 props 传来的值
preCounter: 0,
counter: 0
}
}
static getDerivedStateFromProps(props, state) {
// 跟 state.preCounter 进行比拟
if (props.counter !== state.preCounter) {
return {
counter: props.counter,
preCounter: props.counter
}
}
return null
}
handleClick = () => {
this.setState({counter: this.state.counter + 1})
}
render() {
return (
<div>
<h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
</div>
)
}
}
(3)render
render 是 React 中最外围的办法,一个组件中必须要有这个办法,它会依据状态 state
和属性 props
渲染组件。这个函数只做一件事,就是返回须要渲染的内容,所以不要在这个函数内做其余业务逻辑,通常调用该办法会返回以下类型中一个:
- React 元素:这里包含原生的 DOM 以及 React 组件;
- 数组和 Fragment(片段):能够返回多个元素;
- Portals(插槽):能够将子元素渲染到不同的 DOM 子树种;
- 字符串和数字:被渲染成 DOM 中的 text 节点;
- 布尔值或 null:不渲染任何内容。
(4)componentDidMount()
componentDidMount()会在组件挂载后(插入 DOM 树中)立刻调。该阶段通常进行以下操作:
- 执行依赖于 DOM 的操作;
- 发送网络申请;(官网倡议)
- 增加订阅音讯(会在 componentWillUnmount 勾销订阅);
如果在 componentDidMount
中调用 setState
,就会触发一次额定的渲染,多调用了一次 render
函数,因为它是在浏览器刷新屏幕前执行的,所以用户对此是没有感知的,然而我该当防止这样应用,这样会带来肯定的性能问题,尽量是在 constructor
中初始化 state
对象。
在组件装载之后,将计数数字变为 1:
class App extends React.Component {constructor(props) {super(props)
this.state = {counter: 0}
}
componentDidMount () {
this.setState({counter: 1})
}
render () {
return (
<div className="counter">
counter 值: {this.state.counter} </div>
)
}
}
2)组件更新阶段
当组件的 props
扭转了,或组件外部调用了 setState/forceUpdate
,会触发更新从新渲染,这个过程可能会产生屡次。这个阶段会顺次调用上面这些办法:
- getDerivedStateFromProps
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate
- componentDidUpdate
(1)shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState)
在说这个生命周期函数之前,来看两个问题:
- setState 函数在任何状况下都会导致组件从新渲染吗?例如上面这种状况:
this.setState({number: this.state.number})
- 如果没有调用 setState,props 值也没有变动,是不是组件就不会从新渲染?
第一个问题答案是 会,第二个问题如果是父组件从新渲染时,不论传入的 props 有没有变动,都会引起子组件的从新渲染。
那么有没有什么办法解决在这两个场景下不让组件从新渲染进而晋升性能呢?这个时候 shouldComponentUpdate
退场了,这个生命周期函数是用来晋升速度的,它是在从新渲染组件开始前触发的,默认返回 true
,能够比拟 this.props
和 nextProps
,this.state
和 nextState
值是否变动,来确认返回 true 或者 false
。当返回 false
时,组件的更新过程进行,后续的 render
、componentDidUpdate
也不会被调用。
留神: 增加 shouldComponentUpdate
办法时,不倡议应用深度相等查看(如应用 JSON.stringify()
),因为深比拟效率很低,可能会比从新渲染组件效率还低。而且该办法保护比拟艰难,倡议应用该办法会产生显著的性能晋升时应用。
(2)getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState)
这个办法在 render
之后,componentDidUpdate
之前调用,有两个参数 prevProps
和 prevState
,示意更新之前的 props
和 state
,这个函数必须要和 componentDidUpdate
一起应用,并且要有一个返回值,默认是 null
,这个返回值作为第三个参数传给 componentDidUpdate
。
(3)componentDidUpdate
componentDidUpdate() 会在更新后会被立刻调用,首次渲染不会执行此办法。该阶段通常进行以下操作:
- 当组件更新后,对 DOM 进行操作;
- 如果你对更新前后的 props 进行了比拟,也能够抉择在此处进行网络申请;(例如,当 props 未发生变化时,则不会执行网络申请)。
componentDidUpdate(prevProps, prevState, snapshot){}
该办法有三个参数:
- prevProps: 更新前的 props
- prevState: 更新前的 state
- snapshot: getSnapshotBeforeUpdate()生命周期的返回值
3)组件卸载阶段
卸载阶段只有一个生命周期函数,componentWillUnmount() 会在组件卸载及销毁之前间接调用。在此办法中执行必要的清理操作:
- 革除 timer,勾销网络申请或革除
- 勾销在 componentDidMount() 中创立的订阅等;
这个生命周期在一个组件被卸载和销毁之前被调用,因而你不应该再这个办法中应用 setState
,因为组件一旦被卸载,就不会再装载,也就不会从新渲染。
4)错误处理阶段
componentDidCatch(error, info),此生命周期在后辈组件抛出谬误后被调用。它接管两个参数∶
- error:抛出的谬误。
- info:带有 componentStack key 的对象,其中蕴含无关组件引发谬误的栈信息
React 常见的生命周期如下:React 常见生命周期的过程大抵如下:
- 挂载阶段,首先执行 constructor 构造方法,来创立组件
- 创立实现之后,就会执行 render 办法,该办法会返回须要渲染的内容
- 随后,React 会将须要渲染的内容挂载到 DOM 树上
- 挂载实现之后就会执行 componentDidMount 生命周期函数
- 如果咱们给组件创立一个 props(用于组件通信)、调用 setState(更改 state 中的数据)、调用 forceUpdate(强制更新组件)时,都会从新调用 render 函数
- render 函数从新执行之后,就会从新进行 DOM 树的挂载
- 挂载实现之后就会执行 componentDidUpdate 生命周期函数
- 当移除组件时,就会执行 componentWillUnmount 生命周期函数
React 次要生命周期总结:
- getDefaultProps:这个函数会在组件创立之前被调用一次(有且仅有一次),它被用来初始化组件的 Props;
- getInitialState:用于初始化组件的 state 值;
- componentWillMount:在组件创立后、render 之前,会走到 componentWillMount 阶段。这个阶段我集体始终没用过、十分鸡肋。起初 React 官网曾经不举荐大家在 componentWillMount 里做任何事件、到当初 React16 间接废除了这个生命周期,足见其鸡肋水平了;
- render:这是所有生命周期中惟一一个你必须要实现的办法。一般来说须要返回一个 jsx 元素,这时 React 会依据 props 和 state 来把组件渲染到界面上;不过有时,你可能不想渲染任何货色,这种状况下让它返回 null 或者 false 即可;
- componentDidMount:会在组件挂载后(插入 DOM 树中后)立刻调用,标记着组件挂载实现。一些操作如果依赖获取到 DOM 节点信息,咱们就会放在这个阶段来做。此外,这还是 React 官网举荐的发动 ajax 申请的机会。该办法和 componentWillMount 一样,有且仅有一次调用。
constructor 为什么不先渲染?
由 ES6 的继承规定得悉,不论子类写不写 constructor,在 new 实例的过程都会给补上 constructor。
所以:constructor 钩子函数并不是不可短少的,子组件能够在一些状况略去。比方不本人的 state,从 props 中获取的状况
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 Hook 的应用限度有哪些?
React Hooks 的限度次要有两条:
- 不要在循环、条件或嵌套函数中调用 Hook;
- 在 React 的函数组件中调用 Hook。
那为什么会有这样的限度呢?Hooks 的设计初衷是为了改良 React 组件的开发模式。在旧有的开发模式下遇到了三个问题。
- 组件之间难以复用状态逻辑。过来常见的解决方案是高阶组件、render props 及状态治理框架。
- 简单的组件变得难以了解。生命周期函数与业务逻辑耦合太深,导致关联局部难以拆分。
- 人和机器都很容易混同类。常见的有 this 的问题,但在 React 团队中还有类难以优化的问题,心愿在编译优化层面做出一些改良。
这三个问题在肯定水平上妨碍了 React 的后续倒退,所以为了解决这三个问题,Hooks 基于函数组件 开始设计。然而第三个问题决定了 Hooks 只反对函数组件。
那为什么不要在循环、条件或嵌套函数中调用 Hook 呢?因为 Hooks 的设计是基于数组实现。在调用时按程序退出数组中,如果应用循环、条件或嵌套函数很有可能导致数组取值错位,执行谬误的 Hook。当然,本质上 React 的源码里不是数组,是链表。
这些限度会在编码上造成肯定水平的心智累赘,老手可能会写错,为了防止这样的状况,能够引入 ESLint 的 Hooks 查看插件进行预防。
类组件与函数组件有什么异同?
相同点: 组件是 React 可复用的最小代码片段,它们会返回要在页面中渲染的 React 元素。也正因为组件是 React 的最小编码单位,所以无论是函数组件还是类组件,在应用形式和最终出现成果上都是完全一致的。
咱们甚至能够将一个类组件改写成函数组件,或者把函数组件改写成一个类组件(尽管并不举荐这种重构行为)。从使用者的角度而言,很难从应用体验上辨别两者,而且在古代浏览器中,闭包和类的性能只在极其场景下才会有显著的差异。所以,根本可认为两者作为组件是完全一致的。
不同点:
- 它们在开发时的心智模型上却存在微小的差别。类组件是基于面向对象编程的,它主打的是继承、生命周期等外围概念;而函数组件内核是函数式编程,主打的是 immutable、没有副作用、援用通明等特点。
- 之前,在应用场景上,如果存在须要应用生命周期的组件,那么主推类组件;设计模式上,如果须要应用继承,那么主推类组件。但当初因为 React Hooks 的推出,生命周期概念的淡出,函数组件能够齐全取代类组件。其次继承并不是组件最佳的设计模式,官网更推崇“组合优于继承”的设计概念,所以类组件在这方面的劣势也在淡出。
- 性能优化上,类组件次要依附 shouldComponentUpdate 阻断渲染来晋升性能,而函数组件依附 React.memo 缓存渲染后果来晋升性能。
- 从上手水平而言,类组件更容易上手,从将来趋势上看,因为 React Hooks 的推出,函数组件成了社区将来主推的计划。
- 类组件在将来工夫切片与并发模式中,因为生命周期带来的复杂度,并不易于优化。而函数组件自身轻量简略,且在 Hooks 的根底上提供了比原先更细粒度的逻辑组织与复用,更能适应 React 的将来倒退。
diff 算法如何比拟?
- 只对同级比拟,跨层级的 dom 不会进行复用
- 不同类型节点生成的 dom 树不同,此时会间接销毁老节点及子孙节点,并新建节点
- 能够通过 key 来对元素 diff 的过程提供复用的线索
- 单节点 diff
- 单点 diff 有如下几种状况:
- key 和 type 雷同示意能够复用节点
- key 不同间接标记删除节点,而后新建节点
- key 雷同 type 不同,标记删除该节点和兄弟节点,而后新创建节点