1.说说对 React 的理解?有哪些特性?
raect是什么
React是一个简单的 javascript UI库,用于构建高效、快速的用户界面。
它是一个轻量级库,因此很受欢迎。它遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效。
它使用虚拟 DOM来有效地操作 DOM。
它遵循从高阶组件到低阶组件的单向数据流。
特性
React 特性有很多,如:
JSX 语法
单向数据绑定
虚拟 DOM
声明式编程
Component
优势
高效灵活
声明式的设计,简单使用
组件式开发,提高代码复用率
单向响应的数据流会比双向绑定的更安全,速度更快
2.说说Real diff算法是怎么运作的?
diff算法
在某一时间节点调用 React 的 render() 方法,会创建一棵由 React 元素组成的树。在下一次 state 或 props 更新时,相同的 render() 方法会返回一棵不同的树。React 需要基于这两棵树之间的差别来判断如何有效率的更新 UI 以保证当前 UI 与最新的树保持同步。
React diff策略
1.Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。【永远只比较同层节点,不会跨层级比较节点。】
2.拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
3.对于同一层级的一组子节点,它们可以通过唯一 key 进行区分
基于以上三个前提策略,React 分别对 tree diff、component diff** 以及 element diff 进行算法优化,事实也证明这三个前提策略是合理且准确的,它保证了整体界面构建的性能。
tree层:
tree层对DOM节点的跨层级移动的操作忽略不计,只对相同层级的DOM节点进行比较(即同一个父节点下的所有子节点),一旦发现节点不存在,直接删除掉该节点以及之下的所有子节点。
component层:
component层是组件间的比较,有三种策略:
遇到同一类型的组件遵循 tree diff,进行层级对比
遇到不同类型的组件,直接将这个不同的组件判断为脏组件,并替换该组件和之下所有的子节点
在同一类型的两个组件中,当知道这个组件的虚拟dom没有任何变化,就可以手动使用shouldComponentUpdate()来判断组件是否需要进行diff,进一步的提升了diff效率和性能
注意:
避免使用结构相同但是类型不同的组件,由于类型不同的原因,diff会直接销毁该组件并重建,会造成的性能浪费;
对于同一类型并且没有变化的组件,合理使用 shouldComponentUpdate() 进行优化
element层:
element层对同一层级的元素节点进行比较,有三种情况:
面对全新的节点时,执行插入操作
面对多余的节点时,执行删除操作
面对换位的节点时,执行移动操作
react中key值的作用
key值就是每个元素节点对应的唯一标识,要进行数据的更新,需要进行新旧虚拟dom的对比,就需要对比子元素的key值是否有匹配项,如果有则会进行数据的更新;如果没有就需要进行删除和重新创建
3.说说React生命周期有哪些不同的阶段?每个阶段对应的方法是?
生命周期: React整个组件生命周期包括从创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程
一共有三个阶段,分别为
挂载阶段(Mounting):已插入真实的Dom阶段
更新阶段(Updating):正在被重新渲染的阶段
卸载阶段(Unmounting):已移出真是dom阶段
旧版生命周期
新版生命周期
挂载阶段:
constructor(): 在 React 组件挂载之前,会调用它的构造函数。
componentWillMount(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
componentDidMount(): 在组件挂载后(插入 DOM 树中)立即调用
更新运行阶段:
componentWillReceiveProps(): 在接受父组件改变后的props需要重新渲染组件时用到的比较多,外部组件传递频繁的时候会导致效率比较低
shouldComponentUpdate():用于控制组件重新渲染的生命周期,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新
render(): render() 方法是 class 组件中唯一必须实现的方法。
componentWillUpdate(): shouldComponentUpdate返回true以后,组件进入重新渲染完成之前进入这个函数。
componentDidUpdate(): 每次state改变并重新渲染页面后都会进入这个生命周期
卸载或销毁阶段:
componentWillUnmount (): 在此处完成组件的卸载和数据的销毁。
4.说说你对react hook的理解?
React 中通常使用 类定义 或者 函数定义 创建组件:
在类定义中,我们可以使用到许多 React 特性,例如 state、 各种组件生命周期钩子等,但是在函数定义中,我们却无能为力,因此 React 16.8 版本推出了一个新功能 (React Hooks),通过它,可以更好的在函数定义组件中使用 React 特性。
优势
代码逻辑聚合,逻辑复用
HOC嵌套地狱
代替class
好处:
跨组件复用: 其实 render props / HOC 也是为了复用,相比于它们,Hooks 作为官方的底层 API,最为轻量,而且改造成本小,不会影响原来的组件层次结构和传说中的嵌套地狱;
类定义更为复杂
---- 不同的生命周期会使逻辑变得分散且混乱,不易维护和管理;
---- 时刻需要关注this的指向问题;
---- 代码复用代价高,高阶组件的使用经常会使整个组件树变得臃肿;
状态与UI隔离: 正是由于 Hooks 的特性,状态逻辑会变成更小的粒度,并且极容易被抽象成一个自定义 Hooks,组件中的状态和 UI 变得更为清晰和隔离
注意:
避免在 循环/条件判断/嵌套函数 中调用 hooks,保证调用顺序的稳定;
只有 函数定义组件 和 hooks 可以调用 hooks,避免在 类组件 或者 普通函数 中调用;
不能在useEffect中使用useState,React 会报错提示;
类组件不会被替换或废弃,不需要强制改造类组件,两种方式能并存;
常用的 hooks
useState()
useEffect()
useCallback()
useMemo()
useRef()
useContext()
useReducer()
Hooks的用法
useState():状态钩子
纯函数组件没有状态,useState()用于设置和使用组件的状态属性。
const [state, setState] = useState(initialValue); // state:初始的状态属性,指向状态当前值,类似this.state // setState:修改状态属性值的函数,用来更新状态,类似setState // initialValue:状态的初始值,该值会赋给state
注意:setState的命名为:set+State(初始状态名),并且采用小驼峰命名法。例如[count, setCount]、[name, setName]
示例:使用Hooks重写计数器
const Count = () => { const [count, setCount] = useState(0); // 将0设置为count的初始值 const addCount = () => { let newCount = count; setCount(newCount += 1); } return ( <div> <p>{count}</p> <button onClick={addCount}>加1</button> </div> ) }
用函数组件实现了一个功能完全一样的计数器,代码看起来更加的轻便简洁,没有了继承,没有了渲染逻辑,没有了生命周期等。这就是hooks存在的意义。
useEffect():副作用钩子
useEffect()是副作用的钩子,可以实现特定的功能,如异步请求。语法如下:
useEffect(() => { // 回调函数,其中是要进行的异步操作代码 }, [array]) // [array]:useEffect执行的依赖,当该数组的值发生改变时,回调函数中的代码就会被指向 // 如果[array]省略,则表示不依赖,在每次渲染时回调函数都会执行 // 如果[array]是空数组,即useEffect第二项为[],表示只执行一次
示例:通过useEffect()模拟异步加载数据。
const AsyncPage = () => { // 首先设置loading状态为true const [loading, setLoading] = useState(true); useEffect(() => { // 2秒后将loading状态设置为false setTimeout(() => { setLoading(false); }, 2000); }) return ( // 判断loading是否为true,是就显示loading,不是就显示异步请求完成 loading ? <p>loading...</p> : <p>异步请求完成</p> ) }
示例:useEffect()依赖第二项数组变化
const AsyncPage = ({name}) => { const [loading, setLoading] = useState(true); // 设置loading状态为true const [person, setPerson] = useState({}); // 设置person状态为空对象 useEffect(() => { // 首先设置loading为true,2秒后改为false,name改成传过来的参数 setLoading(true); setTimeout(() => { setLoading(false); setPerson({name}); }, 2000); }, [name]); // 表示当name修改才会执行回调函数 return ( <> {loading ? <p>Loading...</p> : <p>{person.name}</p>} </> ) } const PersonPage = () => { // 设置初始state为空字符串 const [state, setState] = useState(""); const changeName = (name) => { // 修改name的函数 setState(name); } return ( <> {/*首先将state传给name*/} <AsyncPage name={state}/> <button onClick={() => { // 点击按钮后将张三传给name changeName("张三") }}>张三 </button> <button onClick={() => { changeName("李四") }}>李四 </button> </> ) }
useEffect和useLayoutEffect的区别
useEffect()和useLayoutEffect()主要的区别是调用时机不同。
useLayoutEffect()和componentDidMount()及componentDidUpate()一致,再react完成DOM更新后马上同步调用代码,它会阻塞页面的渲染,而useEffect()则会在页面渲染完后才会异步调用。
在实际使用中如果项避免页面抖动,可以把需要操作DOM的代码放在useLayoutEffect()中,在该函数中做DOM操作,这些DOM修改会和react做出的更改一起被一次性渲染到屏幕上,只有一次回流重绘的代价。
useCallback(): 记忆函数
useCallback()为记忆函数,它可以防止因为组件重新渲染,导致方法被重新创建,起到缓存作用。语法如下:
useCallback(() => { // 回调函数,当array改变后,该函数才会重新声明 }, [array]) // 如果[array]为空数组,那么就是第一次创建后就被缓存,如果后期array改变了,拿到的还是老的array // 如果不传入第二个参数,每次都会重新声明一次,拿到的就是最新的array
比如说下面一段代码中,我们可以看到有很多的函数,当我们在return中修改一个状态,就会导致整个页面重新渲染,那么这些函数(handleChange1、handleChange2…)也会被重新创建,这样会造成性能的浪费,因此可以使用useCallback将这些函数缓存下来,这样下一次页面重新渲染的时候,某些函数就不会重新被创建了。
const UseCallback = function () { const handleChange1 = () => { // 具体代码 } const handleChange2 = () => { // 具体代码 } const handleChange3 = () => { // 具体代码 } const handleChange4 = () => { // 具体代码 } return ( <div> {/*具体代码*/} </div> ) }
使用useCallback()时,只需要将其写在整个函数外部即可,上面代码使用useCallback()后效果如下,每当依赖项改变时,该函数才会被重新创建,如果依赖项不变,则不会重新创建。
const UseCallback = function () { const handleChange1 = useCallback( () => { // 具体代码 }, [依赖项] ) const handleChange2 = useCallback( () => { // 具体代码 }, [依赖项] ) const handleChange3 = useCallback( () => { // 具体代码 }, [依赖项] ) const handleChange4 = useCallback( () => { // 具体代码 }, [依赖项] ) return ( <div> {/*具体代码*/} </div> ) }
**useMemo():**记忆组件
useCallback()的功能可以由useMemo()所替代,useMemo()也可以返回一个记忆函数,语法如下:
useMemo(() => fn, []) // useCallback(fn, []) = useMemo(() => fn, [])
useCallback()与useMemo()的区别:
useCallback()不会执行第一个参数函数,而是将其返回,useMemo()会执行第一个函数并且将函数执行结果返回给你。useCallback()常用记忆时间按函数,生成记忆后的时间函数传递给子组件使用,useMemo()更适合经过函数计算得到一个确定的只,比如记忆组件。
**useRef():**保存引用值
useRef()等价于类组件中的React.createRef(),语法如下:
const loginRef = useRef();
使用useRef()创建了一个值后,就可以将其绑定到DOM节点上,给DOM节点增加一个ref属性,将loginRef传入,则可以通过其current属性获取到DOM节点的值,语法如下:
<input ref={loginRef}/>
除此之外,我们都知道useState()可以保存一个状态,那么另一个方法就是使用useRef(),为useRef()传入一个初始值,它可以帮助我们记住这个状态。
useContext():共享状态钩子
useContext()可以共享状态,作用是进行状态的分发(React16.x以后的版本支持),避免了使用Props进行数据的传递。语法如下:
// 第一步:创建全局的Context const AppContext = React.createContext([初始化参数]) // 第二步:通过全局的Context进行状态值的共享 <AppContext.Provider value={{ 属性名: 值 }}> <组件1 /> <组件2 /> </AppContext>
示例:A组件和B组件共享一个状态
const Count = () => { const AppContext = React.createContext({}); const A = () => { const {name} = useContext(AppContext); return ( <div> 我是A组件,我的名字是:{name} </div> ) } const B = () => { const {name} = useContext(AppContext); return ( <div> 我是B组件,我的名字是:{name} </div> ) } return ( <AppContext.Provider value={{name: "橘猫吃不胖"}}> <A/> <B/> </AppContext.Provider> ) }
useReducer():Action钩子
在使用React的过程中,如遇到状态管理,一般会用到Redux,而React本身是不提供状态管理的。而useReducer()提供了状态管理。
useReducer()是useState()的替代方案。首先,关于redux我们都知道,其原理是通过用户在页面中发起action,从而通过reducer方法来改变state,从而实现页面和状态的通信。而Reducer的形式是(state, action) => newstate,返回当前的 state 以及与其配套的 dispatch 方法。。Hooks的useReducer()是这样的:
const [state, dispatch] = useReducer(reducer, initialState)
它接受reducer函数和状态的初始值作为参数,返回一个数组,其中第一项为当前的状态值,第二项为发送action的dispatch函数。
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为可以向子组件传递 dispatch 而不是回调函数。
例如:使用useReducer()实现一个计数器
const Count = () => { const reducer = (state, action) => { if (action.type == "add") { return { ...state, count: state.count + 1 } } else { return state } } const addCount = () => { dispatch({ type: "add" }) } const [state, dispatch] = useReducer(reducer, {count: 0}); return ( <> <p>{state.count}</p> <button onClick={addCount}>加1</button> </> ) }
通过代码可以看到,使用useReducer()代替了Redux的功能,但useReducer无法提供中间件等功能,假如有这些需求,还是需要用到redux。