共计 10609 个字符,预计需要花费 27 分钟才能阅读完成。
怎么用 React.createElement 重写上面的代码
Question:
const element = (
<h1 className="greeting">
Hello, rdhub.cn!
</h1>
);
Answer:
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, rdhub.cn!'
);
组件通信的形式有哪些
- ⽗组件向⼦组件通信: ⽗组件能够向⼦组件通过传 props 的⽅式,向⼦组件进⾏通信
- ⼦组件向⽗组件通信: props+ 回调的⽅式,⽗组件向⼦组件传递 props 进⾏通信,此 props 为作⽤域为⽗组件⾃身的函 数,⼦组件调⽤该函数,将⼦组件想要传递的信息,作为参数,传递到⽗组件的作⽤域中
- 兄弟组件通信: 找到这两个兄弟节点独特的⽗节点, 联合上⾯两种⽅式由⽗节点转发信息进⾏通信
- 跨层级通信: Context 设计⽬的是为了共享那些对于⼀个组件树⽽⾔是“全局”的数据,例如以后认证的⽤户、主题或⾸选语⾔,对于逾越多层的全局数据通过 Context 通信再适宜不过
- 公布订阅模式: 发布者公布事件,订阅者监听事件并做出反馈, 咱们能够通过引⼊ event 模块进⾏通信
- 全局状态治理⼯具: 借助 Redux 或者 Mobx 等全局状态治理⼯具进⾏通信, 这种⼯具会保护⼀个全局状态中⼼ Store, 并依据不同的事件产⽣新的状态
对于 store 的了解
Store 就是把它们分割到一起的对象。Store 有以下职责:
- 维持利用的 state;
- 提供 getState() 办法获取 state;
- 提供 dispatch(action) 办法更新 state;
- 通过 subscribe(listener)注册监听器;
- 通过 subscribe(listener)返回的函数登记监听器
React 中的 props 为什么是只读的?
this.props
是组件之间沟通的一个接口,原则上来讲,它只能从父组件流向子组件。React 具备浓厚的函数式编程的思维。
提到函数式编程就要提一个概念:纯函数。它有几个特点:
- 给定雷同的输出,总是返回雷同的输入。
- 过程没有副作用。
- 不依赖内部状态。
this.props
就是吸取了纯函数的思维。props 的不能够变性就保障的雷同的输出,页面显示的内容是一样的,并且不会产生副作用
为什么应用 jsx 的组件中没有看到应用 react 却须要引入 react?
实质上来说 JSX 是 React.createElement(component, props, ...children)
办法的语法糖。在 React 17 之前,如果应用了 JSX,其实就是在应用 React,babel
会把组件转换为 CreateElement
模式。在 React 17 之后,就不再须要引入,因为 babel
曾经能够帮咱们主动引入 react。
为什么 React 要用 JSX?
JSX 是一个 JavaScript 的语法扩大,或者说是一个相似于 XML 的 ECMAScript 语法扩大。它自身没有太多的语法定义,也不冀望引入更多的规范。
其实 React 自身并不强制应用 JSX。在没有 JSX 的时候,React 实现一个组件依赖于应用 React.createElement 函数。代码如下:
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')
);
而 JSX 更像是一种语法糖,通过相似 XML 的形容形式,刻画函数对象。在采纳 JSX 之后,这段代码会这样写:
class Hello extends React.Component {render() {return <div>Hello {this.props.toWhat}</div>;
}
}
ReactDOM.render(
<Hello toWhat="World" />,
document.getElementById('root')
);
通过比照,能够清晰地发现,代码变得更为简洁,而且代码构造档次更为清晰。
因为 React 须要将组件转化为虚构 DOM 树,所以在编写代码时,实际上是在手写一棵构造树。而XML 在树结构的形容上天生具备可读性强的劣势。
但这样可读性强的代码仅仅是给写程序的同学看的,实际上在运行的时候,会应用 Babel 插件将 JSX 语法的代码还原为 React.createElement 的代码。
总结: JSX 是一个 JavaScript 的语法扩大,构造相似 XML。JSX 次要用于申明 React 元素,但 React 中并不强制应用 JSX。即便应用了 JSX,也会在构建过程中,通过 Babel 插件编译为 React.createElement。所以 JSX 更像是 React.createElement 的一种语法糖。
React 团队并不想引入 JavaScript 自身以外的开发体系。而是心愿通过正当的关注点拆散放弃组件开发的纯正性。
参考 前端进阶面试题具体解答
react-router4 的外围
- 路由变成了组件
- 扩散到各个页面,不须要配置 比方
<link> <route></route>
React 16 中新生命周期有哪些
对于 React16 开始利用的新生命周期:能够看出,React16 自上而下地对生命周期做了另一种维度的解读:
- Render 阶段:用于计算一些必要的状态信息。这个阶段可能会被 React 暂停,这一点和 React16 引入的 Fiber 架构(咱们前面会重点解说)是无关的;
- Pre-commit 阶段:所谓“commit”,这里指的是“更新真正的 DOM 节点”这个动作。所谓 Pre-commit,就是说我在这个阶段其实还并没有去更新实在的 DOM,不过 DOM 信息曾经是能够读取的了;
- Commit 阶段:在这一步,React 会实现实在 DOM 的更新工作。Commit 阶段,咱们能够拿到实在 DOM(包含 refs)。
与此同时,新的生命周期在流程方面,依然遵循“挂载”、“更新”、“卸载”这三个狭义的划分形式。它们别离对应到:
-
挂载过程:
- constructor
- getDerivedStateFromProps
- render
- componentDidMount
-
更新过程:
- getDerivedStateFromProps
- shouldComponentUpdate
- render
- getSnapshotBeforeUpdate
- componentDidUpdate
-
卸载过程:
- componentWillUnmount
redux 的三大准则
-
繁多数据源
整个利用的 state 被存储在一个 object tree 中,并且这个 object tree 之存在惟一一个 store 中
-
state 是只读的
惟一扭转 state 的形式是触发 action,action 是一个用于形容曾经产生工夫的对象,这个保障了视图和网络申请都不能间接批改 state,相同他们只能表白想要批改的用意
- 应用纯函数来执行批改 state
为了形容 action 如何扭转 state tree 须要编写 reduce
react-router 里的 Link 标签和 a 标签的区别
从最终渲染的 DOM 来看,这两者都是链接,都是 标签,区别是∶ <Link>
是 react-router 里实现路由跳转的链接,个别配合 <Route>
应用,react-router 接管了其默认的链接跳转行为,区别于传统的页面跳转,<Link>
的“跳转”行为只会触发相匹配的<Route>
对应的页面内容更新,而不会刷新整个页面。
<Link>
做了 3 件事件:
- 有 onclick 那就执行 onclick
- click 的时候阻止 a 标签默认事件
- 依据跳转 href(即是 to),用 history (web 前端路由两种形式之一,history & hash)跳转,此时只是链接变了,并没有刷新页面而
<a>
标签就是一般的超链接了,用于从以后页面跳转到 href 指向的另一 个页面(非锚点状况)。
a 标签默认事件禁掉之后做了什么才实现了跳转?
let domArr = document.getElementsByTagName('a')
[...domArr].forEach(item=>{item.addEventListener('click',function () {location.href = this.href})
})
哪些办法会触发 React 从新渲染?从新渲染 render 会做些什么?
(1)哪些办法会触发 react 从新渲染?
- setState()办法被调用
setState 是 React 中最罕用的命令,通常状况下,执行 setState 会触发 render。然而这里有个点值得关注,执行 setState 的时候不肯定会从新渲染。当 setState 传入 null 时,并不会触发 render。
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 并不举荐优先思考应用 Context?
- Context 目前还处于试验阶段,可能会在前面的发行版本中有很大的变动,事实上这种状况曾经产生了,所以为了防止给今后降级带来大的影响和麻烦,不倡议在 app 中应用 context。
- 只管不倡议在 app 中应用 context,然而独有组件而言,因为影响范畴小于 app,如果能够做到高内聚,不毁坏组件树之间的依赖关系,能够思考应用 context
- 对于组件之间的数据通信或者状态治理,无效应用 props 或者 state 解决,而后再思考应用第三方的成熟库进行解决,以上的办法都不是最佳的计划的时候,在思考 context。
- context 的更新须要通过 setState()触发,然而这并不是很牢靠的,Context 反对跨组件的拜访,然而如果两头的子组件通过一些办法不影响更新,比方 shouldComponentUpdate() 返回 false 那么不能保障 Context 的更新肯定能够应用 Context 的子组件,因而,Context 的可靠性须要关注
和谐阶段 setState 外部干了什么
- 当调用 setState 时,React 会做的第一件事件是将传递给 setState 的对象合并到组件的以后状态
- 这将启动一个称为和解(
reconciliation
)的过程。和解(reconciliation
)的最终目标是以最无效的形式,依据这个新的状态来更新UI
。为此,React
将构建一个新的React
元素树(您能够将其视为UI
的对象示意) - 一旦有了这个树,为了弄清 UI 如何响应新的状态而扭转,React 会将这个新树与上一个元素树相比拟(diff)
通过这样做,React 将会晓得产生的确切变动,并且通过理解产生什么变动,只需在相对必要的状况下进行更新即可最小化 UI 的占用空间
hooks 罕用的
useEffct 应用:如果不传参数:相当于 render 之后就会执行
传参数为空数组:相当于 componentDidMount
如果传数组:相当于 componentDidUpdate
如果外面返回:相当于 componentWillUnmount
会在组件卸载的时候执行革除操作。effect 在每次渲染的时候都会执行。React 会在执行以后 effect 之前对上一个 effect 进行革除。useLayoutEffect:
useLayoutEffect 在浏览器渲染前执行
useEffect 在浏览器渲染之后执行
当父组件引入子组件以及在更新某一个值的状态的时候,往往会造成一些不必要的节约,而 useMemo 和 useCallback 的呈现就是为了缩小这种节约,进步组件的性能,不同点是:useMemo 返回的是一个缓存的值,即 memoized 值,而 useCallback 返回的是一个 memoized 回调函数。useCallback
父组件更新子组件会渲染, 针对办法不反复执行,包装函数返回函数;useMemo:
const memoizedValue =useMemo(callback,array)
callback 是一个函数用于解决逻辑
array 管制 useMemo 从新执⾏行的数组,array 扭转时才会 从新执行 useMemo
不传数组,每次更新都会从新计算
空数组,只会计算一次
依赖对应的值,当对应的值发生变化时,才会从新计算(能够依赖另外一个 useMemo 返回的值)
不能在 useMemo ⾥面写副作⽤逻辑解决,副作用的逻辑解决放在 useEffect 内进行解决
自定义 hook
自定义 Hook 是一个函数,其名称以“use”结尾,函数外部能够调用其余的 Hook,自定义 Hook 是一种天然遵循 Hook 设计的约定,而并不是 React 的个性
在我看来,自定义 hook 就是把一块业务逻辑独自拿出去写。const [counter, setCounter] = useState(0);
const counterRef = useRef(counter); // 能够保留上一次的变量
useRef 获取节点
function App() {const inputRef = useRef(null);
return <div>
<input type="text" ref={inputRef}/>
<button onClick={() => inputRef.current.focus()}>focus</button>
</div>
}
能够应用 TypeScript 写 React 利用吗?怎么操作?
(1)如果还未创立 Create React App 我的项目
- 间接创立一个具备 typescript 的 Create React App 我的项目:
npx create-react-app demo --typescript
(2)如果曾经创立了 Create React App 我的项目,须要将 typescript 引入到已有我的项目中
- 通过命令将 typescript 引入我的项目:
npm install --save typescript @types/node @types/react @types/react-dom @types/jest
- 将我的项目中任何 后缀名为‘.js’的 JavaScript 文件重命名为 TypeScript 文件即后缀名为‘.tsx’(例如 src/index.js 重命名为 src/index.tsx)
Redux 中异步的申请怎么解决
能够在 componentDidmount 中间接进⾏申请⽆须借助 redux。然而在⼀定规模的项⽬中, 上述⽅法很难进⾏异步流的治理, 通常状况下咱们会借助 redux 的异步中间件进⾏异步解决。redux 异步流中间件其实有很多,当下支流的异步中间件有两种 redux-thunk、redux-saga。
(1)应用 react-thunk 中间件
redux-thunk长处:
- 体积⼩: redux-thunk 的实现⽅式很简略, 只有不到 20 ⾏代码
- 使⽤简略: redux-thunk 没有引⼊像 redux-saga 或者 redux-observable 额定的范式, 上⼿简略
redux-thunk缺点:
- 样板代码过多: 与 redux 自身⼀样, 通常⼀个申请须要⼤量的代码, ⽽且很多都是反复性质的
- 耦合重大: 异步操作与 redux 的 action 偶合在⼀起, 不⽅便治理
- 性能孱弱: 有⼀些理论开发中常⽤的性能须要⾃⼰进⾏封装
应用步骤:
- 配置中间件,在 store 的创立中配置
import {createStore, applyMiddleware, compose} from 'redux';
import reducer from './reducer';
import thunk from 'redux-thunk'
// 设置调试工具
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
// 设置中间件
const enhancer = composeEnhancers(applyMiddleware(thunk)
);
const store = createStore(reducer, enhancer);
export default store;
- 增加一个返回函数的 actionCreator,将异步申请逻辑放在外面
/** 发送 get 申请,并生成相应 action,更新 store 的函数 @param url {string} 申请地址 @param func {function} 真正须要生成的 action 对应的 actionCreator @return {function} */
// dispatch 为主动接管的 store.dispatch 函数
export const getHttpAction = (url, func) => (dispatch) => {axios.get(url).then(function(res){const action = func(res.data)
dispatch(action)
})
}
- 生成 action,并发送 action
componentDidMount(){var action = getHttpAction('/getData', getInitTodoItemAction)
// 发送函数类型的 action 时,该 action 的函数领会主动执行
store.dispatch(action)
}
(2)应用 redux-saga 中间件
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, 学习老本远超 redux-thunk, 最重要的是你的额定学习老本是只服务于这个库的, 与 redux-observable 不同,redux-observable 尽管也有额定学习老本然而背地是 rxjs 和⼀整套思维
- 体积庞⼤: 体积略⼤, 代码近 2000 ⾏,min 版 25KB 左右
- 性能过剩: 实际上并发管制等性能很难⽤到, 然而咱们仍然须要引⼊这些代码
- ts ⽀持不敌对: yield ⽆法返回 TS 类型
redux-saga 能够捕捉 action,而后执行一个函数,那么能够把异步代码放在这个函数中,应用步骤如下:
- 配置中间件
import {createStore, applyMiddleware, compose} from 'redux';
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga'
import TodoListSaga from './sagas'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const sagaMiddleware = createSagaMiddleware()
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware)
);
const store = createStore(reducer, enhancer);
sagaMiddleware.run(TodoListSaga)
export default store;
- 将异步申请放在 sagas.js 中
import {takeEvery, put} from 'redux-saga/effects'
import {initTodoList} from './actionCreator'
import {GET_INIT_ITEM} from './actionTypes'
import axios from 'axios'
function* func(){
try{
// 能够获取异步返回数据
const res = yield axios.get('/getData')
const action = initTodoList(res.data)
// 将 action 发送到 reducer
yield put(action)
}catch(e){console.log('网络申请失败')
}
}
function* mySaga(){
// 主动捕捉 GET_INIT_ITEM 类型的 action,并执行 func
yield takeEvery(GET_INIT_ITEM, func)
}
export default mySaga
- 发送 action
componentDidMount(){const action = getInitTodoItemAction()
store.dispatch(action)
}
(组件的)状态 (state) 和属性 (props) 之间有何不同
State
是一种数据结构,用于组件挂载时所需数据的默认值。State
可能会随着工夫的推移而产生渐变,但少数时候是作为用户事件行为的后果。
Props
(properties 的简写)则是组件的配置。props
由父组件传递给子组件,并且就子组件而言,props
是不可变的(immutable)。组件不能扭转本身的 props,然而能够把其子组件的 props 放在一起(对立治理)。Props 也不仅仅是数据 – 回调函数也能够通过 props 传递。
React 中 constructor 和 getInitialState 的区别?
两者都是用来初始化 state 的。前者是 ES6 中的语法,后者是 ES5 中的语法,新版本的 React 中曾经废除了该办法。
getInitialState 是 ES5 中的办法,如果应用 createClass 办法创立一个 Component 组件,能够主动调用它的 getInitialState 办法来获取初始化的 State 对象,
var APP = React.creatClass ({getInitialState() {
return {
userName: 'hi',
userId: 0
};
}
})
React 在 ES6 的实现中去掉了 getInitialState 这个 hook 函数,规定 state 在 constructor 中实现,如下:
Class App extends React.Component{constructor(props){super(props);
this.state={};}
}
受控组件和非受控组件区别是啥?
- 受控组件 是 React 管制中的组件,并且是表单数据实在的惟一起源。
- 非受控组件是由 DOM 解决表单数据的中央,而不是在 React 组件中。
只管非受控组件通常更易于实现,因为只需应用refs
即可从 DOM 中获取值,但通常倡议优先选择受管制的组件,而不是非受管制的组件。
这样做的次要起因是受控组件反对即时字段验证,容许有条件地禁用 / 启用按钮,强制输出格局。
概述一下 React 中的事件处理逻辑。
为了解决跨浏览器兼容性问题,React 会将浏览器原生事件(Browser Native Event)封装为合成事件(Synthetic Event)并传入设置的事件处理程序中。
这里的合成事件提供了与原生事件雷同的接口,不过它们屏蔽了底层浏览器的细节差别,保障了行为的一致性。另外,React 并没有间接将事件附着到子元素上,而是以繁多事件监听器的形式将所有的事件发送到顶层进行解决(基于事件委托原理)。
这样 React 在更新 DOM 时就不须要思考如何解决附着在 DOM 上的事件监听器,最终达到优化性能的目标。