系列文章目录
目录
前言
react使用
一、JSX
// 原生html
const rawHtml = '<span>富文本内容<i>斜体</i><b>加粗</b></span>';
const rawHtmlData = {
__html: rawHtml, // 注意,必须是这种格式
}
const rawHtmlElem = <div>
<p dangerouslySetInnerHTML={rawHtmlData}></p>
<p>{rawHtml}</p>
</div>
return rawHtmlElem;
二、事件
- event 是 SyntheticEvent,模拟出来 DOM 事件所有能力
- event.nativeEvent 是原生事件对象
- 所有的事件,都被挂载到(16版本 document 上)(17版本 root 组件上)
- 和 DOM 事件不一样,和 Vue事件也不一样
// 获取 event
clickHandle = (event) => {
event.preventDefault() // 阻止默认行为
event.stopPropagation() // 阻止冒泡
console.log('target', event.target) // 指向当前元素,即当前元素触发
console.log('current target', event.currentTarget) // 指向当前元素,假象!
// 注意,event 其实是 React 封装的。可以看 __proto__.constructor 是syntheticEvent 组合事件
console.log('event', event) // 不是原生的Event,原生的 MouseEvent
console.log('event.__proto__.constructor',event.__proto__.constructor)
// 原生 event 如下。其 __proto__.constructor 是 MouseEvent
console.log('nativeEvent',event.nativeEvent)
console.log('nativeEvent target',event.nativeEvent.target) // 指向当前元素,即当前元素触发
console.log('nativeEvent current target',event.nativeEvent.current.target) // 指向document
}
// 传递参数
clickHandlerEvent(id, title, event) {
console.log('event', event); // 最后追加一个参数,即可接收event
}
三、表单
受控组件:表单里的值受state控制
四、setState
不可变值(返回新值,不影响原值)
可能是异步更新 可能会被合并(react <=17)
异步更新+合并state(react18,自动批处理)
// 不要直接修改state,使用不可变值(不修改原值)
// this.state.count++ // 错误
this.setState({
count: this.state.count + 1 //正确
// count: this.state.count
})
// 不可变值(函数式编程,纯函数)-数组
const list5Copy = this.state.list5.slice();
list5Copy.splice(2,0,'a'); // 中间插入、删除
this.setState({
list1: this.state.list1.concat(100), //追加
list2: [...this.state.list2, 100], //追加
list3: this.state.list3.slice(0,3), // 截取
list4: this.state.list4.filter(item => item>100),//筛选
list5: list5Copy //其他
})
// 注意:不能直接对 this.state.list 进行push/pop/splice 等,这样违反不可变值
// 不可变值-对象
this.setState({
obj1: Object.assign({}, this.state.obj1, {a: 100}),
obj2: {...this.state.obj2, a: 100}
})
// 注意:不能直接对 this.state.obj 进行属性设置,这样违反不可变值
// 可能是异步的
this.setState({
count: this.state.count + 1
}, ()=>{
// 回调拿最新值
console.log(this.state.count)
})
console.log(this.state.count); // 异步的,拿不到最新值
// setTimeout中setState是同步的
setTimeout(() => {
this.setState({
count: this.state.count +1
})
console.log(this.state.count)
},0)
// 自己定义的DOM事件,setState是同步的
bodyClickHandler = () => {
this.setState({
count: this.state.count+1
})
}
componentDidMount() {
document.body.addEventListener('click',this.bodyClickHandler)
}
componentWillUnmount() {
// 及时销毁自定义DOM事件
document.body.removeEventListener('click',this.bodyClickHandler)
// clearTimeout
}
// 可能会合并
// 传入对象,会被合并(类似Object.assign),执行结果只一次 +1
this.setState({
count: this.state.count +1
})
this.setState({
count: this.state.count +1
})
this.setState({
count: this.state.count +1
})
// 传入函数,不会被合并,执行结果是+3
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
React <= 17 setState
1. React组件事件:异步更新 + 合并 state
2.DOM事件,setTimeout:同步更新,不合并state
3.只有React组件事件才批处理
React18 setState
1. React组件事件:异步更新 + 合并 state
2.DOM事件,setTimeout:异步更新 + 合并 state
3.Automatic Batching 自动批处理
五、组件声明周期
单组件生命周期
六、React 高级特性
- 函数组件
纯函数,输入props,输出JSX 没有实例,没有生命周期,没有state 不能扩展其他方法
- 非受控组件-优先使用受控组件 必须操作DOM时,再使用非受控组件
// 使用场景 // 1. 必须手动操作DOM元素,setState实现不了 // 2. 文件上传 <input type='file'> // 3. 某些富文本编辑器,需要传入DOM元素 // 优先使用受控组件 必须操作DOM时,再使用非受控组件 // ref // defaultValue defaultChecked // 手动操作 DOM 元素 class App extends React.Component { constructor(props) { super(props) this.state = { name: '豆', flag: true } this.nameInputRef = React.createRef() // 创建ref this.fileInputRef = React.createRef() } render() { return <div> // 使用defaultValue 而不是 value,使用ref <input defaultValue={this.state.name} ref={this.nameInputRef}> // state并不会随着改变 <span>{this.state.name}</span> <button onClick={this.alertName}>click</button> </div> // checkout defaultChecked // return <div> // <input type='checkbox' defaultChecked={this.state.flag}> // </div> // file // return <div> // <input type='file' ref={this.fileInputRef} /> // <button onClick={this.alertFile}>file</button> // </div> } alertName = () => { const elem = this.nameInputRef.current; // 通过ref获取DOM节点 console.log(elem.value) // 不是 this.state.name } alertFile = () => { const elem = this.fileInputRef.current; // 通过ref获取DOM节点 console.log(elem.file[0].name) } }
- Portals(传送门)(组件默认会按照既定层级嵌套渲染,组件渲染到父组件以外使用Portals)
// 使用场景 overflow: hidden 父组件 z-index 值太小 fixed需要放在body第一层 class AdvancedUse extends React.Component { constructor(props) { super(props) } render() { return <> <PortalsDemo>children内容</PortalsDemo> </> } } // PortalsDemo组件 import ReactDOM from 'react-dom'; class PortalsDemo extends React.Component { constructor(props) { super(props) this.state = {} } render() { // 正常 // this.props.children类似vue slot // return <div className='modal'>{this.props.children}</div> // 使用 Protals 渲染到 body 上 // fixed 元素要放在 body 上,有更好的浏览器兼容性 return ReactDom.createProtal( <div className='modal'>{this.props.children}</div>, document.body // DOM节点(渲染到的地方) ) } }
- context(上下文-公共信息,传递给每个组件)
// 创建Context 填入默认值 const ThemeContext = React.createContext('light') class App extends React.Component{ constractor(props) { super(props); this.state = { theme: 'light' } } render() { return <ThemeContext.Provider value={this.state.theme}> <Toolbar /> </ThemeContext.Provider> } } // Toolbar组件 function Toolbar(props) { return ( <ThemedButton /> ) } // 子组件使用class class ThemedButton extends React.Component { // 指定 contextType 读取当前的 theme context // static contextType = ThemeContext // 也可以ThemedButton.contextType = ThemeContext render() { const theme = this.context // React 会往上找到最近的 theme Provider return <p>{theme}</p> } } ThemedButton.contextType = ThemeContext // 制定 contextType 读取当前 theme context // 函数组件 function ThemeLick (props) { // const theme = this.context // 会报错,函数式组件没有实例 // 函数式组件可以使用 Consumer return <ThemeContext.Consumer> {value => <p>{value}</p>} </ThemeContext.Consumer> }
- 异步组件
const ContextDemo = React.lazy(() => import('./ContextDemo')) class App extends React.Component { constructor(props) { super(props) } render() { return <div> <React.Suspense fallback={<div>loading...</div>}> <ContextDemo /> </React.Suspense> </div> } }
- 性能优化
// 1.shouldComponentUpdate(SCU) // 2.PureComponent 和 React.memo // 3.不可变值 immutable.js // react 默认:父组件有更新,子组件则无条件也更新 shouldComponentUpdate(nextProps, nextState) { if(nextState.count !== this.state.count) { return true; //可以渲染 } return false; // 不重复渲染 } // SCU 默认返回true, 即react 默认重新渲染所有子组件 // 必须配合“不可变值”一起使用 // 可先不用 SCU,有性能问题时再考虑使用 // PureComponent ,SCU 中实现浅比较(配合不可变值) class List extends React.PureComponent { constructor(props){ super(props) } render() {} } // memo 函数组件中的 PureComponent function MyComponent(props) { // 使用 props 渲染 } function areaEqual(prevProps, nextPoprs) { // 如果把nextPoprs 传入 render 方法的返回结果与 将 prevProps 传入render方法的返回结果一致则返回true 否则返回 false } export default React.memo(MyComponent, areaEqual) // immutable.js // 基于共享数据(不是深拷贝),速度好 const map1 = Immutable.Map({a:1,b:2,c:3}); const map2 = map1.set('b',50); map1.get('b'); // 2 map2.get('b'); // 50
- 高阶组件HOC
// 高阶组件不是一种功能,而是一种模式 // 接收一个组件,返回一个新组件 const HOCFactory = (Component) => { class HOC extends React.Componet { // 在此定义多个组件的公共逻辑 render (){ return <Component {...this.props} /> // 返回拼装的结果 } } return HOC } const EnhancedComponent1 = HOCFactory(WrappedComponent1); const EnhancedComponent2 = HOCFactory(WrappedComponent2);
- Render Props
// Render Props 的核心思想 // 通过一个函数将class 组件的 state 作为 porps 传递给纯函数组件 class Factory extends React.Component { constructor() { this.state = { // state 即多个组件的公共逻辑的数据 } } // 修改 state render() { return <div>{this.props.render(this.state)}</div> } } // 属性校验 Factory.propTypes = { render: PropTypes.func.isRequired // 必须接收一个render属性,而且是函数 } const App = (props) => { <Factory render={ // render 是一个函数组件 (props) => <p> {props.a} {props.b} ... </p> }/> }
HOC vs Render Props
HOC:模式简单,但会增加组件层级(透传可能有属性覆盖)
Render Props:代码简洁,学习成本较高
七、Redux
Redux是一个数据状态管理插件
单项数据流
redux 更新过程:dispatch 一个 action,reducer接收,判断action,返回新的state,触发订阅更新,绑定到视图层
dispatch(action)
reducer -> newState(不可变值)
subscribe 触发通知
// redux 单独使用
import { createStore } from 'redux'
export default function() {
// 第一步:定义计算规则,即reducer
function counter(state = 0, action){
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'decrement':
return state - 1
default:
return state
}
}
// 第二步:根据计算规则生成store
let store = createStore(counter)
// 第三步:定义数据(即 state)变化之后的派发规则
store.subscribe(() => {
console.log('fn1 -> current state', store.getState())
})
store.subscribe(() => {
console.log('fn2 -> current state', store.getState())
})
store.subscribe(() => {
console.log('fn2 -> current state', store.getState())
})
// 第四步:触发数据变化(dispatch一个action)数据变化后触发更新订阅
store.dispatch({type: 'INCREMENT'})
store.dispatch({type: 'INCREMENT'})
store.dispatch({type: 'DECREMENT'})
}
react-redux
// <Provider>
// connect
// mapStateToProps mapDispatchToProps
import React from 'react'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
let store = createStore(todoApp)
export default fuunction() {
return <Provider store={store}>
<App />
</Provider>
}
异步action
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// 创建store 时,作为中间件传入 redux-thunk
const store = createStore(rootReducer, applyMiddleware(thunk));
// 同步action
export const addTo = text => {
// 返回action对象
return { type: 'ADD', id: '123'}
}
// 异步 action
export const addTodoAsync = text => {
// 返回函数,其中有 dispatch 参数
return (dispatch) => {
// ajax 异步获取数据
fetch(url).then(res => {
// 执行异步 action
dispatch(addTo(res.text))
})
}
}
redux 中间件(常用中间件 redux-thunk/redux-promise/redux-saga)
view触发dispatch action,dispatch可能有中间件会再次触发action,reducer根据action返回新的state,新的state触发视图更新
八、React-router
路由模式(hash-to B、H5 history-to C)
路由配置(动态路由、懒加载)
获取url中的参数,可以使用this.props.params.xx
// hash
import { HashRouter as Router, Switch, Route } from 'react-router-dom'
// history
import { BrowserRouter as Router, Switch, Route} from 'react=router-dom'
动态路由
跳转路由(<Link to='/' /> useHistory)
懒加载
针对大型项目的静态资源懒加载问题,react-router 也给出了解决方案 —— huge-apps,它将 react-router 本身和 webpack 的 require.ensure
结合起来,就解决了这一问题。
九、性能优化
性能检测
安装react性能检测工具 npm i react-addons-perf --save,然后在 ./app/index.js中
// 性能测试
import Perf from 'react-addons-perf'
if(__DEV__){
window.Perf = Perf
}
运行程序。在操作之前先运行Perf.start()
开始检测,然后进行若干操作,运行Perf.stop
停止检测,然后再运行Perf.printWasted()
即可打印出浪费性能的组件列表。在项目开发过程中,要经常使用检测工具来看看性能是否正常。
PureRenderMixin优化
React 最基本的优化方式是使用PureRenderMixin,安装工具 npm i react-addons-pure-render-mixin --save
,然后在组件中引用并使用
import React from 'react'
import PureRenderMixin from 'react-addons-pure-render-mixin'
class List extends React.Component {
constructor(props, context) {
super(props, context);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
// 重写组件的shouldComponentUpdate 函数,在每次更新之前判断props和state,如果有变化则返回true,无变化返回false
}
}
Immutable.js 优化
层级很深,考虑用Immutable
var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50
总结
react基本使用知识点