最全的(前端知识)汇总,以便自身复习
上一篇内容:JS/ES5->ES6
由于篇幅过于长,知识体系过于庞杂,请谨慎学习, 转述的我都有放置链接(难理解的也放了demo测试或者图)
技术规范(基础了解,大佬尽可跳过)
React
通信问题
父-->子
父组件传递到子组件,符合了react的单向数据流理念,自上而下的传递props
-
父组件
class Parent extends Component { ......省略 render(){ let {value}=this.state; return( <Child value={value} ></Child> ) } } -
子组件
class Child extends Component { ......省略 render(){ let {value}=this.props; return( <span>{value}</span> ) } }
子-->父
依赖props的引用,通过父组件传递给子组件的回调函数【父组件属性为函数】来实现
-
父组件
class Parent extends Component { ......省略 setValue = value => { this.setState({ value, }) } render() { return ( <div> //父组件传递给子组件setValue的方法【回调函数 】 <Child setValue={this.setValue} /> </div> ); } } -
子组件
class Child extends Component { ......省略 handleClick = () => { //接收父组件传递过来的方法 const { setValue } = this.props; //子组件的value当做父组件的setValue参数回调回去 setValue(this.value); } render() { return ( <div> 我是Child <div className="card"> //传递参数【回调】 <div className="button" onClick={this.handleClick}>通知</div> </div> </div> ); } }
兄弟组件
-
利用共有的Container【容器或者说父组件】
-
Container(容器)
// container class Container extends Component { constructor() { super(); this.state = { value: '', } } setValue = value => { //改变容器数据 this.setState({ value, }) } render() { return ( <div> //A组件回调函数设置改变容器的值(子--》父通信) <A setValue={this.setValue}/> //B组件获取容器的数据(父--》子通信) <B value={this.state.value} /> </div> ); } } -
A组件
class A extends Component { 。。。省略 handleClick = () => { const { setValue } = this.props; setValue(this.value); } render() { return ( <div className="card"> <div className="button" onClick={this.handleClick}>通知</div> </div> ) } } -
B组件
const B = props => ( return( <div className="card"> {props.value} </div> ) ); export default B;
-
跨级组件通信(非父子):Context(16.x有全新的API)
- 老版本Context
想要Context发挥作用【去实现跨级组件的通信问题】,先解决何时使用,避免滥用Context带来的组件复用性变差的影响
-
Context就像javascript中的全局变量,只有真正全局的东西才适合放在context中
-
顶层组件A
//引入类型检查 import PropTypes from 'prop-types'; // Component A class A extends React.Component { // add the following method getChildContext() { return { user: this.props.user } } render() { <MiddleComponent /> } } // add the following property A.childContextTypes = { user: PropTypes.string }-
底层跨级组件D
// Component D class D extends React.Component { render() { return <div>{this.context.user}</div> } } // add the following property D.contextTypes = { user: PropTypes.string }
-
-
注意事项
- 底层跨级组件,需要声明需要使用的Context属性,才能访问父组件Context对象的属性,否则,即使属性名没写错,拿到的对象也是undefined
- 随着你的应用程序不断增长,你可以通过类型检查捕获大量错误,配置特定的 propTypes 属性,React-proptypes的类型检查
- Context发挥作用,需要两种组件:一个是Context生产者(Provider),通常是一个父节点------另外是一个Context的消费者(Consumer),通常是一个或者多个子节点。所以Context的使用基于生产者消费者模式
- 确保Context是可控的,使用Context并无大碍,甚至如果能够合理的应用,Context其实可以给React组件开发带来很强大的体验
- 受到生命周期shouldComponentUpdate的影响
借鉴参考文:
聊一聊我对 React Context 的理解以及应用 -----Context-----React学习(10)—— 高阶应用:上下文(Context)
- 新版本Context
新版本的React context使用了Provider和Customer模式,和react-redux的模式非常像。在顶层的Provider中传入value, 在子孙级的Consumer中获取该值,并且能够传递函数,用来修改context
-
React.createContext的方式创建了一个Context实例
export const themes = { light: { foreground: '#000000', background: '#eeeeee', }, dark: { color: '#ffffff', background: '#222222', }, }; export const ThemeContext = React.createContext( themes.dark // 默认值 ); -
顶层组件
import {ThemeContext} from './theme-context'; class Message extends React.Component { render(){ return( <ThemeContext.Provider> //中间组件 <Title /> </ThemeContext.Provider> ); } } -
中间组件(跨层级的通信,这边不处理数据【这里要处理数据不需要context】)
Title(props) { return ( <div> <Text /> </div> ); } -
消费数据组件 (底层跨级组件 )
class Text extends React.Component { render () { return ( <ThemeContext.Consumer> {context => ( <h1 style={{ color: context.color,background: context.background}}> {this.props.children} </h1> )} </ThemeContext.Consumer> ); } } -
了解点
-
Provider
这里的 Provider 类似 react-redux 中的 Provider 组件,用来注入全局的 data (允许 Consumer 订阅 Context 的变化)。一个 Provider 可以连接到多个 Consumer。
-
Consumer
Consumer 组件,表示要消费 Provider 传递的数据(订阅 Context 的响应组件)。当 Provider 发生变化的时候,所有的 Consumer 都会被 re-rendered
-
你可以在任何生命周期中访问到它,包括 render 函数中
class MyClass extends React.Component { componentDidMount() { let value = this.context; /* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */ } componentDidUpdate() { let value = this.context; /* ... */ } componentWillUnmount() { let value = this.context; /* ... */ } render() { let value = this.context; /* 基于 MyContext 组件的值进行渲染 */ } } MyClass.contextType = MyContext; -
消费多个 Context
// Theme context,默认的 theme 是 “light” 值 const ThemeContext = React.createContext('light'); // 用户登录 context const UserContext = React.createContext({ name: 'Guest', }); class App extends React.Component { render() { const {signedInUser, theme} = this.props; // 提供初始 context 值的 App 组件 return ( <ThemeContext.Provider value={theme}> <UserContext.Provider value={signedInUser}> <Layout /> </UserContext.Provider> </ThemeContext.Provider> ); } } function Layout() { return ( <div> <Sidebar /> <Content /> </div> ); } // 一个组件可能会消费多个 context function Content() { return ( <ThemeContext.Consumer> {theme => ( <UserContext.Consumer> {user => ( <ProfilePage user={user} theme={theme} /> )} </UserContext.Consumer> )} </ThemeContext.Consumer> ); } -
在嵌套组件中更新 Context
export const themes = { light: { foreground: '#000000', background: '#eeeeee', }, dark: { color: '#ffffff', background: '#222222', }, }; // 确保传递给 createContext 的默认值数据结构是调用的组件(consumers)所能匹配的! export const ThemeContext = React.createContext({ theme: themes.dark, toggleTheme: () => {}, }); ------------------------------------------------------------------------------ import {ThemeContext} from './theme-context'; function ThemeTogglerButton() { // Theme Toggler 按钮不仅仅只获取 theme 值,它也从 context 中获取到一个 toggleTheme 函数 return ( <ThemeContext.Consumer> {({theme, toggleTheme}) => ( <button onClick={toggleTheme} style={{backgroundColor: theme.background}}> Toggle Theme </button> )} </ThemeContext.Consumer> ); } export default ThemeTogglerButton; ------------------------------------------------------------------- import {ThemeContext, themes} from './theme-context'; import ThemeTogglerButton from './theme-toggler-button'; class App extends React.Component { constructor(props) { super(props); //更新函数 this.toggleTheme = () => { this.setState(state => ({ theme: state.theme === themes.dark ? themes.light : themes.dark, })); }; // State 也包含了更新函数,因此它会被传递进 context provider。 this.state = { theme: themes.light, toggleTheme: this.toggleTheme, }; } render() { // 整个 state 都被传递进 provider(传递映射给了ThemeContext) return ( <ThemeContext.Provider value={this.state}> <Content /> </ThemeContext.Provider> ); } } function Content() { return ( <div> <ThemeTogglerButton /> </div> ); } ReactDOM.render(<App />, document.root); -
参考资料:
React 16.3来了:带着全新的Context API------ 全新的 React Context API-------React官方文档
-
-
通过发布/订阅者模式来实现(可以自己实现/引入第三方库别人写好的)
PubSubJS【类似于Vue公交总站(只是vue他内部自己实现了)】
-
订阅(订阅消息)PubSub.subscrib(名称,函数)
import PubSub from 'pubsub-js' PubSub.subscribe('delete',function(msg,data){})-
示例
componentDidMount(){ this.pubsub_token = PubSub.subscribe('PubSubmessage', (topic,message)=> { this.setState({ increase: message }); }); }
-
-
发布(发送消息)PubSub.publish(名称,参数)
import PubSub from 'pubsub-js' PubSub.publish('delete',data);-
示例
buttonDecrease(){ PubSub.publish('PubSubmessage', this.state.decrease); } <button onClick={this.buttonIncrease.bind(this)}>Increase</button>
-
-
取消订阅:PubSub.unsubscrib(名称)
import PubSub from 'pubsub-js' PubSub.unsubscribe(name);-
示例
componentWillUnmount(){ PubSub.unsubscribe(this.pubsub_token); }
-
-
Redux
Redux使用(React-redux)
"只有遇到 React 实在解决不了的问题,你才需要 Redux 。"
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性
-
(组件角度)应用场景
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
-
Redux 的适用场景:多交互、多数据源。
-
用户的使用方式复杂
-
不同身份的用户有不同的使用方式(比如普通用户和管理员)
-
多个用户之间可以协作
-
与服务器大量交互,或者使用了WebSocket
-
View要从多个来源获取数据
-
使用redux-saga
从redux-thunk到redux-saga,本质都是为了解决异步action的问题
-
数据如何获取?
-
建立仓库,把仓库(数据事件)state在入口文件(顶层容器注入)
- createSagaMiddleware():函数是用来创建一个 Redux 中间件,将 Sagas 与 Redux Store 链接起来
sagas 中的每个函数都必须返回一个 Generator 对象,middleware 会迭代这个 Generator 并执行所有 yield 后的 Effect(Effect 可以看作是 redux-saga 的任务单元)
-
middleware.run(sagas, ...args):动态执行sagas,用于applyMiddleware阶段之后执行sagas
-
方式一:
import {createStore, applyMiddleware} from 'redux' import createSagaMiddleware from 'redux-saga' import reducers from './reducers' import rootSaga from './rootSaga' // 创建一个saga中间件 const sagaMiddleware = createSagaMiddleware() // 创建store const store = createStore( reducers, 将sagaMiddleware 中间件传入到 applyMiddleware 函数中 applyMiddleware(sagaMiddleware) ) // 动态执行saga,注意:run函数只能在store创建好之后调用 sagaMiddleware.run(rootSaga) export default store-
与之配合的入口文件
import React from 'react'; import ReactDOM from 'react-dom'; import {Provider} from 'react-redux'; import store from './redux/store/store'; import App from './App' ReactDOM.render( <Provider store={store}>//属性store随意改的 <App /> </Provider>, document.getElementById('root') );
-
-
-
方式二:
//利用createStore把reducer数据中心的属性或者方法映射到store,之后通过Provider传递给组件 //applyMiddleware,添加中间件 /* *applyMiddleware 函数的作用就是对 store.dispatch 方法进行增强和改造, *使得在发出 Action 和执行 Reducer 之间添加其他功能。 */ import { createStore, applyMiddleware } from "redux"; import createSagaMiddleware from "redux-saga"; import logger from "redux-logger"; import mySaga from "../sagas"; import reducer from "../reducers"; //实例化redux-saga的createSagaMiddleware创建一个saga方法中间件 const sagamiddleware = createSagaMiddleware(); //配置仓库 export default () => { //sagamiddleware中间件加入到middlewaress数组 const middlewares = [sagamiddleware]; //React Native中有一个全局变量__DEV__用于指示当前运行环境是否是开发环境 if (__DEV__) { //开发环境引入logger middlewares.push(logger); } //引用中间件创建仓库 const createStoreMiddleware = applyMiddleware(...middlewares)(createStore); //创建数据中心连接仓库 // const store = createStore(reducer, applyMiddleware(...middlewares)); const store = createStoreMiddleware(reducer); // console.log(store); //运行saga,或者这样写sagaMiddleware.run(mySaga),mySaga是引入的saga文件 sagamiddleware.run(mySaga); return store; };-
与之配合的入口文件
import React from 'react'; import ReactDOM from 'react-dom'; import {Provider} from 'react-redux'; import storeConfig from './redux/store/store'; import App from './App' let store = storeConfig(); ReactDOM.render( <Provider store={store}> //属性store随意改的 <App /> </Provider>, document.getElementById('root') );
-
-
行为(或者事件)【派发任务】
-
变量不分离---(saga中间件,异步请求不再actions这里处理)
const News = { GET_MANY_NEWS: "GET_MANY_NEWS" }; //action这边只定义(规范)所有的行为类型type export function fetchNewData(parmas) { return { type: News.GET_MANY_NEWS, parmas }; } -
变量分离
constants/xxx.ts
export const BANNERARRAY: string = "BANNERARRAY"actions/xxx.ts---(saga中间件,异步请求不再actions这里处理)
import {BANNERARRA} from constants/xxx.ts export function fetchNewData(parmas) { return { type: BANNERARRA, parmas }; }
-
-
制定了行为之后(设置该行为到底做什么异步动作?)【也就是这里使用了中间件redux-saga】redux-saga文档 /// 从redux-thunk到redux-saga实践 /// 解决防抖/延迟等 Redux-saga学习笔记
监听 action 来执行有副作用的 task,以保持 action 的简洁性。并且引入了 sagas 的机制和 generator 的特性,让redux-saga 非常方便地处理复杂异步问题 redux-saga 是一个 redux 中间件,意味着这个线程可以通过正常的 redux action 从主应用程序启动,暂停和取消,它能访问完整的 redux state,也可以 dispatch redux action
普及redux-saga的api
Effect 是一个 javascript 对象,里面包含描述副作用的信息,可以通过 yield 传达给 sagaMiddleware 执行
在 redux-saga 世界里,所有的 Effect 都必须被 yield 才会执行,所以有人写了 eslint-plugin-redux-saga 来检查是否每个 Effect 都被 yield。并且原则上来说,所有的 yield 后面也只能跟Effect,以保证代码的易测性。
例如:yield fetch(UrlMap.fetchData); 应该用 call Effect : yield call(fetch, UrlMap.fetchData) -
关于各个 常用Effect 的具体介绍:官方api
-
take(pattern):等待 dispatch 匹配某个 action
take函数可以理解为监听未来的action,它创建了一个命令对象,告诉middleware等待一个特定的action, Generator会暂停,直到一个与pattern匹配的action被发起,才会继续执行下面的语句,也就是说,take是一个阻塞的 effect
function* watchFetchData() { while(true) { /* 监听一个type为 'FETCH_REQUESTED' 的action的执行, 直到等到这个Action被触发, 才会接着执行下面的 yield fork(fetchData) 语句 /? yield take('FETCH_REQUESTED'); yield fork(fetchData); } } -
put(action)
put函数是用来发送action的 effect,你可以简单的把它理解成为redux框架中的dispatch函数,当put一个action后,reducer中就会计算新的state并返回,注意: put 也是阻塞 effect
export function* toggleItemFlow() { let list = [] // 发送一个type为 'UPDATE_DATA' 的Action,用来更新数据,参数为 `data:list` yield put({ type: actionTypes.UPDATE_DATA, data: list }) } -
call(fn,args)
call函数你可以把它简单的理解为就是可以调用其他函数的函数,它命令 middleware 来调用fn 函数, args为函数的参数,注意: fn 函数可以是一个 Generator 函数,也可以是一个返回 Promise 的普通函数,call 函数也是阻塞 effect(会等返回结果后执行后面的语句)
export const delay = ms => new Promise(resolve => setTimeout(resolve, ms)) export function* removeItem() { try { // 这里call 函数就调用了 delay 函数,delay 函数为一个返回promise 的函数 return yield call(delay, 500) } catch (err) { yield put({type: actionTypes.ERROR}) } } -
fork(fn, ...args)
fork 函数和 call 函数很像,都是用来调用其他函数的,但是fork函数是非阻塞函数,也就是说,程序执行完 yield fork(fn, args) 这一行代码后,会立即接着执行下一行代码语句,而不会等待fn函数返回结果后
import { fork } from 'redux-saga/effects' export default function* rootSaga() { // 下面的四个 Generator 函数会一次执行,不会阻塞执行 yield fork(addItemFlow) yield fork(removeItemFlow) yield fork(toggleItemFlow) yield fork(modifyItem) }-
select(selector, ...args)
select 函数是用来指示 middleware调用提供的选择器获取Store上的state数据,你也可以简单的把它理解为redux框架中获取store上的 state数据一样的功能 :store.getState()
export function* toggleItemFlow() { // 通过 select effect 来获取 全局 state上的 `getTodoList` 中的 list let tempList = yield select(state => state.getTodoList.list) } -
all([...effects])
创建一个 Effect 描述信息,用来命令 middleware 并行地运行多个 Effect,并等待它们全部完成。这是与标准的 Promise#all 相当对应的 API。
import { fetchCustomers, fetchProducts } from './path/to/api' import { all, call } from `redux-saga/effects` function* mySaga() { const [customers, products] = yield all([ call(fetchCustomers), call(fetchProducts) ]) } -
cancel(task): 是一个无阻塞 Effect。也就是说,Generator 将在取消异常被抛出后立即恢复。
-
-
-
Saga 辅助函数
-
takeEvery
-
用来监听action,每个action都触发一次,如果其对应是异步操作的话,每次都发起异步请求,而不论上次的请求是否返回。
-
takeEvery 允许多个 fetchData 实例同时启动,在某个特定时刻,我们可以启动一个新的 fetchData 任务, 尽管之前还有一个或多个 fetchData 尚未结束
import { takeEvery } from 'redux-saga' function* watchFetchData() { yield* takeEvery("FETCH_REQUESTED", fetchData) }
-
-
takeLatest
如果我们只想得到最新那个请求的响应(例如,始终显示最新版本的数据),我们可以使用 takeLatest 辅助函数
import { takeLatest } from 'redux-saga' function* watchFetchData() { yield* takeLatest('FETCH_REQUESTED', fetchData) }和takeEvery不同,在任何时刻 takeLatest 只允许执行一个 fetchData 任务,并且这个任务是最后被启动的那个,如果之前已经有一个任务在执行,那之前的这个任务会自动被取消
-
-
sagas
import https from "@/api"; import { all, call, put, takeLatest } from "redux-saga/effects"; export function* newsList(actions: any) { try { const data = yield call(https.newsLists, actions.parmas); yield put({ type: "GETSUCCESS", data }); } catch (err) { yield put({ type: "GETFAIL" }); } } export default function* root() { // 监听GET_MANY_NEWS行为事件newsList yield all([takeLatest("GET_MANY_NEWS", newsList)]); }-
多个saga统一执行
import { all, fork } from "redux-saga/effects"; import home from "./home"; //暴露所有的saga function* rootSaga() { yield all([ fork(home) ]); } export default rootSaga; -
reducers处理数据中心
const initState = { newsList: [] }; //数据处理中心(通过方法/请求数据等都需要经过reducer) //基本从actions来 const Newslist = (state = initState, actions: any) => { //接收到Succsee,就把接收到的数据存放到newsList // console.log(actions.data); switch (actions.type) { case "GETSUCCESS": //合并数据到data里 return { ...state, newsList: actions.data }; case "GETFAIL": //合并数据到data里 return state; default: return state; } }; export default Newslist; + 多个reducer统一执行 import { combineReducers } from "redux"; import home from "./home"; //暴露到入口文件存放到仓库 export default combineReducers({ home }); -
组件里该如何使用呢?英文官方文档
-
法一 bindActionCreators(commonAction, dispatch)映射到actions,映射到fetchNewData行为,传递参数,调用函数事件,Data通过this.props.Data获取【commonAction可以分解,拿出需要的事件】
//获取数据(新闻) getList = (page: number) => { //这边只发起请求 const { actions: { fetchNewData } } = this.props; fetchNewData({ page, limit: 8 }); }; const mapStateToProps = (state, props) => ({ Data: state.home.newsList //传递数据到Data,子组件接收到data }) //commonAction是定义的行为 const mapDispatchToProps = dispatch => ( {actions: bindActionCreators(commonAction, dispatch) }) //withRouter添加history export default withRouter(connect( mapStateToProps, mapDispatchToProps )(Cart)) -
方式二
-
return bindActionCreators(commonAction, dispatch)获取到所有的行为方法,然后就可以是使用所有方法
-
commonAction可以把里面的事件需要的拿出来import {fetchNewData} from 'xxx'
-
return bindActionCreators({fetchNewData}, dispatch)
//获取数据(新闻) getList = (page: number) => { //这边只发起请求 fetchNewData({ page, limit: 8 }); }; const mapStateToProps = (state, props) => ({ Data: state.home.newsList //传递数据到Data,子组件接收到data }) //commonAction是定义的行为 const mapDispatchToProps = dispatch => ( return bindActionCreators(commonAction, dispatch) ) //withRouter添加history export default withRouter(connect( mapStateToProps, mapDispatchToProps )(Cart))
-
-
-
-
-
reudx-thunk reudx-saga唯一区别actions的解耦性
-
法一
//方法允许传递参数 export const getListData=(payload)=>{ return{ type:types.GETLISTS, payload:payload } } export function fetchData(params={page:1}){ //dispath函数当作参数 return (dispath)=>{ fetch( `http://localhost:3000/person?_page=${params.page}&_limit=4`).then(res=>{ return res.json(); }).then(data=>{ //异步dispath dispath(getListData(data)) }) } }-
法二(异步直接耦合)
import { BANNERARRAY } from "../constants/findSong"; import http from "@/https/index.ts"; // 获取发现页轮播 export const getBannerArray = () => { return dispatch => { http.findBanner().then(res => { if (res.statusCode == 200) { let bannerData = res.data.banners; dispatch({ type: BANNERARRAY, payload: { bannerData } }); } }); }; };
-
-
入口文件中间件换成redux-thunk就行
-
-
-
redux的reateStore,combineReducers,bindActionCreators,applyMiddleware源码分析 /// 探究redux源码-衍生-中间件思想 /// Redux源码深度解读
Immutable(Immutable.js)reducer会用到多一点
Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。【来减少内存占用(和垃圾回收的失效)】
Immutable.js 以及在 react+redux 项目中的实践
-
formJS(),将 JavaScript Object 和 Array 彻底转换为 Immutable Map 和 List
-
is(),与 Object.is() 类似都是对值的比较,但它会将 Immutable Iterable 视为值类型数据而不是引用类型数据,如果两个 Immutable Iterable 的值相等,则返回 true。与 Object.is() 不同的是,is(0, -0) 的结果为 true
-
List,有序索引集,类似于 JavaScript 中的 Array
-
Map,无序 Iterable,读写 Key 的复杂度为 O(log32 N)
-
OrderedMap,有序 Map,排序依据是数据的 set() 操作
-
Set,元素为独一无二的集合,添加数据和判断数据是否存在的复杂度为 O(log32 N)
-
OrderedSet,有序 Set,排序依据是数据的 add 操作。
-
Stack,有序集合,且使用 unshift(v) 和 shift() 进行添加和删除操作的复杂度为 O(1)
-
Range(),返回一个 Seq.Indexed 类型的数据集合,该方法接收三个参数 (start = 1, end = infinity, step = 1),分别表示起始点、终止点和步长,如果 start 等于 end,则返回空的数据结合
-
Repeat(),返回一个 Seq.indexed 类型的数据结合,该方法接收两个参数 (value,times),value 表示重复生成的值,times 表示重复生成的次数,如果没有指定 times,则表示生成的 Seq 包含无限个 value
-
Record,用于衍生新的 Record 类,进而生成 Record 实例。Record 实例类似于 JavaScript 中的 Object 实例,但只接收特定的字符串作为 key,且拥有默认值
-
Seq,序列(may not be backed by a concrete data structure)
-
Iterable,可以被迭代的 (Key, Value) 键值对集合,是 Immutable.js 中其他所有集合的基类,为其他所有集合提供了 基础的 Iterable 操作函数(比如 map() 和 filter)
-
Collection,创建 Immutable 数据结构的最基础的抽象类,不能直接构造该类型
React提供了PureRenderMixin(新版本弃用)和React.PureComponent,但是它们只提供了prop和state的浅比较方式,并不能深层比较新旧属性是否相同。
为什么不实现深层比较呢?因为如果prop或者state比较复杂,深层比较性能会很差。所以只做浅比较是一个相对比较合理的折中。
Immutable可以很好地解决这个问题,不仅可以深层比较数据是否发生变化,而且效率很高。
新的shouldComponentUpdate实现如下:
import {is} from 'immutable';
shouldComponentUpdate(nextProps = {}, nextState = {}) {
const thisProps = this.props || {};
const thisState = this.state || {};
if (Object.keys(thisProps).length !== Object.keys(nextProps).length ||
Object.keys(thisState).length !== Object.keys(nextState).length) {
return true;
}
for (const key in nextProps) {
if (!is(thisProps[key], nextProps[key])) {
return true;
}
}
for (const key in nextState) {
if (!is(thisState[key], nextState[key])) {
return true;
}
}
return false;
}
React 路由(俩个版本的路由)
-
react-router路由@3.2.3【npm i react-router-dom react-router@3.2.3 -s】
-
router/index.js (路由部分)
-
懒加载,通过React模块的Suspense与lazy
-
Router包裹Route,Route:属性path与component->与相应组件对应
-
路由器Router就是React的一个组件【Router组件本身只是一个容器,真正的路由要通过Route组件定义】
import { Router, Route, hashHistory } from 'react-router'; render(( <Router history={hashHistory}> <Route path="/" component={App}/> </Router> ), document.getElementById('app'));
-
-
嵌套路由,Route嵌套Route
-
路由
<Router history={hashHistory}> <Route path="/" component={App}> <Route path="/repos" component={Repos}/> <Route path="/about" component={About}/> </Route> </Router> -
App组件(this.props.children表示嵌套的路由)
render() { return <div> {this.props.children} </div> }
-
-
主页显示,不需要path,需要引入react-router--IndexRoute
-
IndexRoute 组件:指定默认情况下加载的子组件。你可以把IndexRoute想象成某个路径的index.html
import React,{Suspense} from "react"; import {Router,Route,hashHistory,IndexRoute} from 'react-router' //一起引入,通过index.js // import {Home,About,App,Repo,Rep} from '../components/' //懒加载拆分 const App=React.lazy(()=>import('../components/App')); const Home=React.lazy(()=>import('../components/Home')); const About=React.lazy(()=>import('../components/About')); const Repo=React.lazy(()=>import('../components/Repo')); const Rep=React.lazy(()=>import('../components/Rep')); export default class Index extends React.Component{ render(){ return( <Suspense fallback={<div>Loding....</div>}> <Router history={hashHistory}> <Route path="/" component={App}> {/* 主页设置为默认,利用IndexRoute */} <IndexRoute component={Home} /> <Route path="/About" component={About}/> <Route path="/repo" component={Repo}> <Route path="/repo/:username/:reponame" component={Rep}/> </Route> </Route> </Router> </Suspense> ) } } ------------------------------------------------------------------ export App from './App.js' export Home from './Home.js' export About from './About.js' export Repo from './Repo.js' export Rep from './Rep.js'
-
-
-
通配符(path属性可以使用通配符)
<Route path="/hello/:name"> // 匹配 /hello/michael // 匹配 /hello/ryan <Route path="/hello(/:name)"> // 匹配 /hello // 匹配 /hello/michael // 匹配 /hello/ryan <Route path="/files/*.*"> // 匹配 /files/hello.jpg // 匹配 /files/hello.html <Route path="/files/*"> // 匹配 /files/ // 匹配 /files/a // 匹配 /files/a/b <Route path="/**/*.jpg"> // 匹配 /files/hello.jpg // 匹配 /files/path/to/file.jpg -
Link:Link组件用于取代a元素,生成一个链接,允许用户点击后跳转到另一个路由。它基本上就是a元素的React 版本,可以接收Router的状态。
-
activeClassName/activeStyle,可以设置选中高亮【别忘了设置css】,精确匹配OnlyActiveOnIndex
render() { return <div> <ul role="nav"> <Link to="/about" activeStyle={{color: 'red'}}>About</Link> <Link to="/repos" activeClassName="active">Repos</Link> </ul> </div> } -
封装Link
import {Link} from 'react-router'; import React from 'react' const NavLink =(props)=>{ return( <Link {...props} activeClassName="active" ></Link> ) } export default NavLink;
-
-
IndexLink:如果链接到根路由/,不要使用Link组件,而要使用IndexLink组件
-
根路由来说,activeStyle和activeClassName会失效,或者说总是生效,因为/会匹配任何子路由。而IndexLink组件会使用路径的精确匹配
<IndexLink to="/" activeClassName="active"> Home </IndexLink>
-
-
Redirect 组件:路由的跳转,即用户访问一个路由,会自动跳转到另一个路由
<Route path="inbox" component={Inbox}> {/* 从 /inbox/messages/:id 跳转到 /messages/:id */} <Redirect from="messages/:id" to="/messages/:id" /> </Route> 现在访问/inbox/messages/5,会自动跳转到/messages/5。 -
IndexRedirect 组件:访问根路由的时候,将用户重定向到某个子组件(第一次访问不进入主页)
<Route path="/" component={App}> <IndexRedirect to="/welcome" /> <Route path="welcome" component={Welcome} /> <Route path="about" component={About} /> </Route> 上面代码中,用户访问根路径时,将自动重定向到子组件welcome。-
App
import React,{Component} from "react"; import NavLink from './NavLink' import {IndexLink} from 'react-router' class App extends Component{ render(){ console.log(this.props) return ( <div style={{width:'100%',height:'50px',background:'#cdcdcd'}}> 导航 {/* 设置主页链接 */} <IndexLink to="/" activeClassName="active">home</IndexLink> <NavLink to="/about">about</NavLink> <NavLink to="/repo">repo</NavLink> <hr/> {/* 类似slot的显示子组件的,也就是路由,嵌套路由下的东西 */} <h3>以下是内容</h3> {this.props.children} </div> ) } } export default App; -
this.props.params能获取到路由参数
-
hashHistory.push():跳转组件
-
histroy 属性
- hashHistory,路由将通过URL的hash部分(#)切换
- browserHistory,浏览器的路由就不再通过Hash完成了,而显示正常的路径example.com/some/path
- createMemoryHistory主要用于服务器渲染。它创建一个内存中的history对象,不与浏览器URL互动
-
路由的钩子
-
进入路由【onEnter钩子还可以用来做认证】
<Route path="inbox" component={Inbox}> <Route path="messages/:id" onEnter={ ({params}, replace) => replace(`/messages/${params.id}`) } /> </Route>
-
-
-
react-router路由4x---5x 【npm i react-router-dom react-router -s】基本使用
-
main.js--入口文件
-
需要用到react-router-dom--HashRouter/BrowserRouter俩种模式
-
Switch用到switch来切换路由,包裹起来
-
exact,路由需要精确匹配
-
重定向,不给path
-
利用map提取单独拿出来的路由数组
import React from "react"; import ReactDOM from "react-dom"; import "./main.css"; import {Home,BasicRouting,Nav,Blocking,Miss,Address,Query,Recursive} from './components' import {HashRouter as Router,Route,Switch} from 'react-router-dom' // import routes from './router' const Index=()=>{ return( <Router> <Nav /> <Switch> {/* { routes.map((route,idx)=>{ return <Route path={route.path} component={route.component} exact={route.exact} /> }) } */} <Route path="/" component={Home} exact/> <Route path="/basic" component={BasicRouting} /> <Route path="/block" component={Blocking} /> <Route path="/miss" component={Miss} /> <Route path="/query" component={Query} /> <Route path="/recursive" component={Recursive} /> <Route component={Address} /> </Switch> </Router> ) } ReactDOM.render(<Index />, document.getElementById("app")
-
提取路由
import React from "react"; import {Home,BasicRouting,Blocking,Miss,Query,Recursive} from './components' module.exports=[ { path:'/', component:Home, exact:true }, { path:'/basic', component:BasicRouting }, { path:'/block', component:Blocking }, { path:'/miss', component:Miss }, { path:'/query', component:Query }, { path:'/recursive', component:Home, exact:Recursive } ]-
组件 Route 属性 strict 末尾斜杠的匹配
====》路由请求末尾必须带 /
<Route strict path="/about/" component={About} /> -
组件 Route 属性 exact 完全匹配==
====》exact=false 的时候 path 等于 /about /about/me 都能匹配 ====》exact=true 的时候 只匹配 path 等于 /about
<Route path="/about" component={About} /> -
组件 Link:生成路由链接
-
组件 NavLink:生成路由链接的基础上,如果是当前路由设置激活样式【activeClassName="selected"】
<div style={{width:'100%',height:'50px',background:'#cdcdcd'}}> 导航 <NavLink to="/" activeClassName="active" exact>Home</NavLink> <NavLink to="/basic">BasicRouting</NavLink> <NavLink to="/block">Blocking</NavLink> <NavLink to="/miss">Miss</NavLink> <NavLink to="/query">Query</NavLink> <NavLink to="/recursive">Recursive</NavLink> </div> -
组件 Switch:只渲染出第一个与当前访问地址匹配的 Route 或 Redirect
-
组件 Redirect路由重定向:
<Switch> <Redirect from='/users/:id' to='/users/profile/:id'/> <Route path='/users/profile/:id' component={Profile}/> </Switch> //当请求 /users/:id 被重定向去 '/users/profile/:id' -
组件 Prompt:当用户离开当前页面前做出一些提示
import React,{Component} from 'react' import {Prompt} from "react-router-dom" export default class Blocking extends Component{ render(){ return( <div style={{width:'100%',height:'50px',background:'#cdcdcd'}}> <h1>Blocking</h1> <Prompt message="你确定要离开吗"/> </div> ) } } -
路由传递参数(设参/传参)
<NavLink to={{ pathname:this.props.match.url+"/level3", search:"?aaa=222&bbb=1111" }}>level3</NavLink> -
获取路由参数(接参使用参数)
this.props.location this.props.match.params -
嵌套组件
import React,{Component} from 'react' import {Route,NavLink} from 'react-router-dom' import {Content,Address} from './index' export default class BasicRouting extends Component{ render(){ return( <div style={{width:'100%',height:'250px',background:'#cdcdcd'}}> <ul> <li><NavLink to={this.props.match.url+"/adc/level1"}>level1</NavLink></li> <li><NavLink to={this.props.match.url+"/level2"}>level2</NavLink></li> <li><NavLink to={this.props.match.url+"/adc/level3"}>level3</NavLink></li> </ul> <Route path={`${this.props.match.url}/adc/:level`} component={Content} /> </div> ) } } -
递归自调用自己(嵌套自己)
-
-
-
-
生命周期钩子函数
-
React 15.x
-
初始化(initialization)
-
getDefaultProps(设置默认props)---->getInitialState(初始化state)
-
getDefaultProps
-
这个方法只会调用一次,该组件类的所有后续应用,getDefaultPops 将不会再被调用,其返回的对象可以用于设置默认的 props
-
getInitialState 和 getDefaultPops 的调用是有区别的,getDefaultPops 是对于组件类来说只调用一次,后续该类的应用都不会被调用,而 getInitialState 是对于每个组件实例来讲都会调用,并且只调一次。
getDefaultProps: function(){ return { name: 'pomy', git: 'dwqs' } }
-
-
getInitialState
-
对于组件的每个实例来说,这个方法的调用**有且只有一次,**用来初始化每个实例的 state,在这个方法里,可以访问组件的 props。
getInitialState: function() { return {liked: false}; },
-
-
-
-
数据挂载(当组件在客户端被实例化)
-
componentWillMount 首次渲染执行前立即调用且仅调用一次。如果在这个方法内部调用 setState 并不会触发重新渲染,这也是在 render 方法调用之前修改 state 的最后一次机会
-
render(返回的结果并不是真正的DOM元素,而是一个虚拟的表现,类似于一个DOM tree的结构的对象)
该方法会创建一个虚拟DOM,用来表示组件的输出。对于一个组件来讲,render方法是唯一一个必需的方法。render方法需要满足下面几点:
- 只能通过 this.props 和 this.state 访问数据(不能修改)
- 可以返回 null,false 或者任何React组件
- 只能出现一个顶级组件,不能返回一组元素
- 不能改变组件的状态
- 不能修改DOM的输出
-
componentDidMount:
该方法被调用时,已经渲染出真实的 DOM,可以再该方法中通过 this.getDOMNode() 访问到真实的 DOM(推荐使用 ReactDOM.findDOMNode())
-
-
数据更新过程(组件已经渲染好并且用户可以与它进行交互,比如鼠标点击,手指点按,或者其它的一些事件,导致应用状态的改变)
-
componentWillReceiveProps
组件的 props 属性可以通过父组件来更改,这时,componentWillReceiveProps 将来被调用。可以在这个方法里更新 state,以触发 render 方法重新渲染组件
-
shouldComponentUpdate:确定组件的 props 或者 state 的改变不需要重新渲染
-
componentWillUpdate
这个方法和 componentWillMount 类似,在组件接收到了新的 props 或者 state 即将进行重新渲染前,componentWillUpdate(object nextProps, object nextState) 会被调用,注意不要在此方面里再去更新 props 或者 state
-
render
-
componentDidUpdate
这个方法和 componentDidMount 类似,在组件重新被渲染之后,componentDidUpdate(object prevProps, object prevState) 会被调用。可以在这里访问并修改 DOM。
-
-
销毁时
-
componentWillUnmount
每当React使用完一个组件,这个组件必须从 DOM 中卸载后被销毁,此时 componentWillUnmout 会被执行,完成所有的清理和销毁工作,在 conponentDidMount 中添加的任务都需要再该方法中撤销,如创建的定时器或事件监听器。
-
-
当再次装载组件时,以下方法会被依次调用
- getInitialState
- componentWillMount
- render
- componentDidMount
-
-
React 16.3
-
初始化(initialization)
-
static defaultProps(props初始化)---》constructor(state初始化)
-
static defaultProps
static defaultProps={ age:'aa' } -
constructor
constructor(props) { super(props); this.state={ color:"white" } }
-
-
-
数据挂载:-----与React 15x一样
-
数据更新的时候:-----与React 15x一样
-
销毁时:-----与React 15x一样
-
当再次装载组件时,以下方法会被依次调用
- constructor
- componentWillMount
- render
- componentDidMount
-
-
React-- 16.4
-
数据初始化:-----与React 15.3一样
-
数据挂载(当组件在客户端被实例化)
-
static getDerivedStateFromProps
-
在组件创建时和更新时的render方法之前调用,它应该返回一个对象来更新状态,或者返回null来不更新任何内容
-
即 state 的值在任何时候都取决于 props
getSnapshotBeforeUpdate(prevProps, prevState) -
派生状态会导致代码冗余,并使组件难以维护。 确保你已熟悉这些简单的替代方案:
-
如果你需要执行副作用(例如,数据提取或动画)以响应 props 中的更改,请改用 componentDidUpdate。
-
如果只想在 prop 更改时重新计算某些数据,请使用 memoization helper 代替。了解JavaScript中的Memoization以提高性能,再看React的应用
-
如果你想在 prop 更改时“重置”某些 state,请考虑使组件完全受控或使用 key 使组件完全不受控 代替。受控组件与非受控组件 /// React组件的受控和非受控
-
-
-
render(返回的结果并不是真正的DOM元素,而是一个虚拟的表现,类似于一个DOM tree的结构的对象)
该方法会创建一个虚拟DOM,用来表示组件的输出。对于一个组件来讲,render方法是唯一一个必需的方法。render方法需要满足下面几点:
- 只能通过 this.props 和 this.state 访问数据(不能修改)
- 可以返回 null,false 或者任何React组件
- 只能出现一个顶级组件,不能返回一组元素
- 不能改变组件的状态
- 不能修改DOM的输出
-
componentDidMount:
该方法被调用时,已经渲染出真实的 DOM,可以再该方法中通过 this.getDOMNode() 访问到真实的 DOM(推荐使用 ReactDOM.findDOMNode())
-
-
数据更新的时候
-
static getDerivedStateFromProps
-
shouldComponentUpdate:确定组件的 props 或者 state 的改变不需要重新渲染
-
render
-
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate() 被调用于render之后,可以读取但无法使用DOM的时候。它使您的组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)。此生命周期返回的任何值都将作为参数传递给componentDidUpdate()。
-
componentDidUpdate
这个方法和 componentDidMount 类似,在组件重新被渲染之后,componentDidUpdate(object prevProps, object prevState) 会被调用。可以在这里访问并修改 DOM。
-
-
-
React 16.8.6
-
props只接受-----static defaultProps
-
useState-------constuctor this.state
-
useEffect---------componentDidMount,componentDidUpdate,
-
componentDidMount,componentDidUpdate,和 componentWillUnmount
-
React.memo-------shouldComponentUpdate
//它帮助我们控制何时重新渲染组件Character //检查接下来的渲染是否与前一次的渲染相同,如果两者是一样的,那么就会保留上一次的渲染结果 export default React.memo(Character,(prevProps,nextProps)=>{ return nextProps.selectedChar===prevProps.selectedChar });
-
如何使用请看hooks
高阶组件(HOC)- higher-order components
面试系列之二:你真的了解React吗(上)如何设计组件以及重要的生命周期
首先要明白我们为什么需要高阶组件:【没法操作组件中的组件的】
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
具体而言,高阶组件是参数为组件,返回值为新组件的函数
- 官方文档规范
HOC 不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用。
HOC 不需要关心数据的使用方式或原因,而被包装组件也不需要关心数据是怎么来的 文档
不要试图在 HOC 中修改组件原型(或以其他方式改变它)文档
HOC 为组件添加特性。自身不应该大幅改变约定。HOC 返回的组件与原组件应保持类似的接口。 文档
最大化可组合性 文档
包装显示名称以便轻松调试 文档
- 官方文档注意事项
动态调用 HOC。你可以在组件的生命周期方法或其构造函数中进行调用。【不要在 render 方法中使用 HOC】文档
务必复制静态方法 文档
Refs 不会被传递 Refs 不会被传递
-
先来一个最简单的高阶组件
import React, { Component } from 'react'; import simpleHoc from './simple-hoc'; class Usual extends Component { render() { console.log(this.props, 'props'); return ( <div> Usual </div> ) } } export default simpleHoc(Usual); ------------------------------------------------------------------ import React, { Component } from 'react'; const simpleHoc = WrappedComponent => { console.log('simpleHoc'); return class extends Component { render() { return <WrappedComponent {...this.props}/> } } } export default simpleHoc;组件Usual通过simpleHoc的包装,打了一个log... 那么形如simpleHoc就是一个高阶组件了,通过接收一个组件class Usual,并返回一个组件class。
-
两种形式(俩种形式能做到什么)
-
属性代理(Props Proxy):【操作props/refs获取组件实例/抽离state/用其他元素包裹 WrappedComponent(包装组件)】
-
操作 props【读取、添加、编辑、删除】
-
添加props
import React, { Component } from 'react'; const propsProxyHoc = WrappedComponent => class PP extends Component { const newProps = { uid, }; render() { return (<WrappedComponent {...this.props} {...newProps} />); } }; export default propsProxyHoc; -
删除props
import React, { Component } from 'react'; const propsProxyHoc = WrappedComponent => class PP extends Component { const {uid,...otherProps}=this.props; render() { return (<WrappedComponent {...otherProps} />); } }; export default propsProxyHoc;
-
-
refs获取组件实例
import React, { Component } from 'react'; const propsProxyHoc = WrappedComponent => class PP extends Component { render() { return (<WrappedComponent {...this.props} ref={instanceComponent=>this.instanceComponent=instanceComponent} />); } }; export default propsProxyHoc; -
抽离state:将所有的状态的管理交给外面的容器组件,这个模式就是 抽取状态 外面的容器就是这个高阶组件(state【包括事件】在高阶组件里)-------------这里用的比较多的就是react处理表单的时候
@xxxx--》看react修饰器 用修饰器优雅的使用高阶组件
-------当然兼容性是存在问题的,通常都是通过babel去编译的。 babel提供了plugin,高阶组件用的是类装饰器,所以用transform-decorators-legacy babel
+ 高阶组件 import React, { Component } from 'react'; constHocContainer = (WrappedComponent) => class PP extends React.Component { constructor(props) { super(props) this.state = { name: '' } } onNameChange = (event) => { this.setState({ name: event.target.value }) } render() { const newProps = { name: { value: this.state.name, onChange: this.onNameChange } } return <WrappedComponent {...this.props} {...newProps} /> } } + 组件 import React, { Component } from 'react'; import HocContainer from 'xxxx' @HocContainer classSampleComponent extends React.Component { render() { return <input name="name" {...this.props.name}/> } }这里我们把state,onChange等方法都放到HOC里,其实是遵从的react组件的一种规范,子组件简单,傻瓜,负责展示,逻辑与操作放到Container
-
用其他元素包裹 WrappedComponent(包装组件) 为了封装样式、布局或别的目的,你可以用其它组件和元素包裹 WrappedComponent
constHocStyleComponent = (WrappedComponent, style) => class PP extends React.Component { render() { return ( <div style={style}> <WrappedComponent {...this.props} {...newProps} /> </div> ) } }这样子使用:
importHocStyleComponent from './HocStyleComponent'; const colorSytle ={color:'#ff5555'} const newComponent = HocStyleComponent(SampleComponent, colorSytle);
代理方式下WrappedComponent会经历一个完整的生命周期,产生的新组件和参数组件是两个不同的组件,一次渲染,两个组件都会经历各自的生命周期 在继承方式下,产生的新组件和参数组件合二为一,super.render只是生命周期中的函数,变成一个生命周期
-
-
继承方式
-
【操纵生命周期(渲染劫持)】
-
高阶组件:继承方式下,产生的新组件和参数组件合二为一,super.render只是生命周期中的函数,变成一个生命周期
import * as React from 'react'; constHocComponent = (WrappedComponent) => classMyContainer extends WrappedComponent { render() { if (this.props.time && this.state.success) { return super.render()//劫持了render函数 } return <div>倒计时完成了...</div> } } -
组件
import * as React from 'react'; importHocComponent from './HocComponent' @HocComponent classDemoComponent extends React.Component { constructor(props) { super(props); this.state = { success: true, }; } render() { return <div>我是一个组件</div> } } exportdefaultDemoComponent; -
操纵prop【也可以控制state,限制这样做,可能会让WrappedComponent组件内部状态变得一团糟。建议可以通过重新命名state,以防止混淆】
constHOCPropsComponent = (WrappedComponent) => class PP extends WrappedComponent { render() { const elementsTree = super.render(); let newProps = { color: (elementsTree && elementsTree.type === 'div') ? '#fff' : '#ff5555' }; const props = Object.assign({}, elementsTree.props, newProps) const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.children) return newElementsTree } }
-
-
-
-
彩蛋recompose库:它提供了很多很有用的高阶组件(小工具),而且也可以优雅的组合它们。
-
Render Props
Render Props(Function as Child) 也是一种常见的 react 模式, 比如官方的 Context API 和 react-spring 动画库. 目的高阶组件差不多: 都是为了分离关注点, 对组件的逻辑进行复用; 在使用和实现上比高阶组件要简单, 在某些场景可以取代高阶组件
React 并没有限定任何 props 的类型, 所以 props 也可以是函数形式. 当 props 为函数时, 父组件可以通过函数参数给子组件传递一些数据进行动态渲染
<FunctionAsChild>{() => <div>Hello,World!</div>}</FunctionAsChild>-
把特定行为或功能封装成一个组件,提供给其他组件使用让其他组件拥有这样的能力
-
流程:封装行为或者功能成一个组件==》根据业务props(函数)事件(传参)==》最后调用行为组件
const Bar = ({ title }) => (<p>{title}</p>); //接收函数式(title) => <Bar title={title} />的props==><Bar title="我是一个state的属性" /> //最后返回<p>我是一个state的属性</p>---->实现的就是显示<p>title</p> class Foo extends React.Component { constructor(props) { super(props); this.state = { title: '我是一个state的属性' }; } render() { const { render } = this.props; const { title } = this.state; return ( <div> {render(title)} </div> ) } } class App extends React.Component { render() { return ( <div> <h2>这是一个示例组件</h2> <Foo render={(title) => <Bar title={title} />} /> </div> ); } } ReactDOM.render(<App />, document.getElementById('app'))
-
-
在没有hooks之前高阶组件与props render是俩大利器
- 使用组件的方式来抽象业务逻辑 /// 模态框管理
Suspense
Suspense允许组件在渲染之前“等待”某些东西。今天,Suspense只支持一个用例:动态加载组件React.lazy。将来,它将支持其他用例,如数据获取。
// Usage of Clock
const Clock = React.lazy(() => {
console.log("start importing Clock");
return import("./Clock");
});
这里我们使用了React.lazy, 这样就能实现代码的懒加载。 React.lazy 的参数是一个function, 返回的是一个promise. 这里返回的是一个import 函数, webpack build 的时候, 看到这个东西, 就知道这是个分界点。 import 里面的东西可以打包到另外一个包里。
<Suspense fallback={<Loading />}>
{ showClock ? <Clock/> : null}
</Suspense>
React.lazy采用必须调用动态的函数import()。这必须返回一个Promise解析为具有default包含React组件的导出的模块。
----》查看路由的懒加载--》在路由
React最新API---HOOKs【第三种状态共享方案】(未来趋势,Vue3.0也会出hooks)
React Hooks 要解决的问题是状态共享,是继 render-props 和 higher-order components 之后的第三种状态共享方案,不会产生 JSX 嵌套地狱问题
这个状态指的是状态逻辑,所以称为状态逻辑复用会更恰当,因为只共享数据处理逻辑,不会共享数据本身。
先说说内部提供的hooks
-
useState:初始化state, setState能够修改值
const [state, setState] = useState(initialState); -
示例
const [page, setPage] = useState(1); //初始化定义页码为1 setPage(2); -
useEffect:componentDidMount,componentDidUpdate,和 componentWillUnmount结合
-
示例(用好驱动依赖)
useEffect(() => { //componentDidMount //初始化位置列表 resetAnimate(); return () => { //componentWillUnmount // 清除订阅 subscription.unsubscribe(); }; //componentDidUpdate }, [props.ListData]); //返回数据变动作为驱动依赖
-
-
useContext:前面提到的React 新的 Context API,这个 API 不是完美的,在多个 Context 嵌套的时候尤其麻烦。
比如,一段 JSX 如果既依赖于 ThemeContext 又依赖于 LanguageContext,那么按照 React Context API 应该这么写:
<ThemeContext.Consumer> { theme => ( <LanguageContext.Cosumer> language => { //可以使用theme和lanugage了 } </LanguageContext.Cosumer> ) } </ThemeContext.Consumer>因为 Context API 要用 render props,所以用两个 Context 就要用两次 render props,也就用了两个函数嵌套,这样的缩格看起来也的确过分了一点点。
使用 Hooks 的 useContext,上面的代码可以缩略为下面这样:
const theme = useContext(ThemeContext); const language = useContext(LanguageContext); // 这里就可以用theme和language了也不完美-----组件总会在 context 值变化时重新渲染,如果context里只是size变了,也会造成重新渲染(shouldComponentUpdate)
-
额外的 Hook
-
useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
-
示例:React Hooks实现异步请求实例—useReducer、useContext和useEffect代替Redux方案
const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); } -
useLayoutEffect
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
-
useRef
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变
function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { // `current` 指向已挂载到 DOM 上的文本输入元素 inputEl.current.focus(); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }
-
。。。。。等等
-
很长一段时间,高阶组件和 render props 是组件之间共享逻辑的两个武器,但如同我前面章节介绍的那样,这两个武器都不是十全十美的,现在 Hooks 的出现,也预示着高阶组件和 render props 可能要被逐步取代。
在 Hooks 兴起之后,共享代码之间逻辑会用函数形式,而且这些函数会以 use- 前缀为约定,重用这些逻辑的方式,就是在函数形式组件中调用这些 useXXX 函数。
一篇看懂 React Hooks /// 2019年了,整理了N个实用案例帮你快速迁移到React Hooks(收藏慢慢看系列) /// React Hooks全面理解教程