由于之前的《React 使用过程中常见问题》已经比较旧,现结合最近小伙伴们遇到的问题整理出第二版:),如下:
目录
React 中的 useEffect 怎么用?
React 中的 useMemo 怎么用?
现代 React 函数组件的 ref 如何直接操控?
React Hook 到底是什么鬼?
React 设置多个 className
React 如何覆盖子组件的样式
React 中 useState 中的 setState 设值是异步的如何在设完值后获取最新值?
React 中 useState 中的 setState 的另一种用法
React 中如何使用类 componentDidMount 及如何取到 useEffect 中被监听变量的旧值?
React 中如何使用 React.createContext 进行组件之间由外到内单向传值?
React 版 Ant Design 3.x 的那些坑 1.Popconfirm
React 版 Ant Design 3.x 的那些坑 2.Form.Item 里的 getFieldDecorator
熟悉 React 顶层 API,如 cloneElement isValidElement React.Children
react 兼容 IE9 +
React 页面数据守卫(路由、浏览器关闭、浏览器崩溃)
Create React App 项目 eject 后常见配置问题
credentials,withCredentials 接口请求配置
Webpack 中编写自定义 loader 和 plugin, 及如何调试 (debug) webpack, create-react-app 等脚手架
React 中的 useSelector 怎么用?useSelector 取出的值永远是旧值,不更新怎么处理?
React 中的 useState,组件深层次使用时,值为数组类型,更新内部某孙值时,Dom UI 不更新怎么处理?
React 中的 useEffect 怎么用?
主要是用来实现监听状态变量的变化而执行相应的回调事件,代码如下:
useEffect(() => { // 处理自定义事情 }, [variable1, variable2, variableX]); // 被 watch 的对象
注:如果不传任何变量或者,传空数组,则等效于 React
的 componentDidMount
或者 Vue
的 onMount
。
React 中的 useMemo 怎么用?
主要是用来实现监听状态变量的变化而实时处理自定义计算返回最新的计算值,代码如下:
const myComputedData = useMemo(() => { let result; // 处理自定义计算 // 如: result = variable1 + variable2 + variableX; return result; }, [variable1, variable2, variableX]);
总之:与 Vue
的 Computed
的效果差不多。
现代 React 函数组件的 ref 如何直接操控?
React
的函数组件 ref
操作起来相对麻烦点。当然如果是 React
的类组件使用 ref
其实与 Vue
差不多。但现在还在用 React
组件的写法已经不香了。所以麻烦点也是值得的,主要依赖于:wrappedComponentRef
, useRef
, useImperativeHandle
, forwardRef
,代码如下:
// 子组件 const ChildComp = forwardRef((props, ref) => { // ... 其它代码 useImperativeHandle(ref, () => ({ // 供父组件使用的函数 outFn1: () => { // 这里编写自定义代码 return xxx; }, // 供父组件使用的变量 outData1, outData2, outDataX, })); return ( <> <div>content1</div> <div>content2</div> </> ); }); // 父组件 const ParentComp = (props) => { const childRef = useRef({}); // mouted 或者按需时使用子组件传过来的变量或者函数 useEffect(() => { const { outData1, outData2, outDataX, outFn1, } = childRef.current; // 这里编写自定义代码 ... }, []); return ( <div className={style.parent}> <ChildComp wrappedComponentRef={childRef} /> </div> ); };
除此之外,还有一种情况可以用 ref
,参考官网:https://reactjs.org/docs/refs-and-the-dom.html
You can, however, use the ref attribute inside a function component as long as you refer to a DOM element or a class component:
function CustomTextInput(props) { // textInput must be declared here so the ref can refer to it const textInput = useRef(null); function handleClick() { textInput.current.focus(); } return ( <div> <input type="text" ref={textInput} /> <input type="button" value="Focus the text input" onClick={handleClick} /> </div> ); }
React Hook 到底是什么鬼?
Mixin
或者 Vue 跟进的 Hook。由最底层来看,Hook
其实顾名思义是一个勾子,一个函数,创建了一个 React 组件组件闭包,返回一些变量或者函数供 React 组件内部使用,由于使用了一系列 Reactive
的变量数据的变动及流量都是响应式的,非常灵活方便。
React 设置多个 className
className={`title ${index === this.state.active ? 'active' : ''}`}
方法二:join(“”)
className={["title", index === this.state.active?"active":null].join(' ')}
方法三:classnames (需要下载 classnames)
const classNames = require('classnames'); const Button = React.createClass({ // ... render () { const btnClass = classNames({ btn: true, 'btn-pressed': this.state.isPressed, 'btn-over': !this.state.isPressed && this.state.isHovered }); return <button className={btnClass}>{this.props.label}</button>; } });
参考引用:https://blog.csdn.net/qq_35605231/article/details/84974029
React 如何覆盖子组件的样式
:global
,但要注意不要污染全局,要带上当前页面的 wrap 样式
.wrap { :global { /* 按需加需要覆盖的样式 */ } } /* 或者 */ :global(.myTargetClassName) { /* 按需加需要覆盖的样式 */ }
React 中 useState 中的 setState 设值是异步的如何在设完值后获取最新值?
ref
ref.current = targetState
const [data, setData] = useState({}); const refData = useRef({}); refData.current = data; setData({a: 1}); // 设值 console.log(data); // => {},取不到 setTimeout(()=>{ console.log(data); // => {},还是取不到 console.log(refData.current); // => {a: 1},取到最新值了 :) }, 1000);
React 中 useState 中的 setState 的另一种用法
useState
hook 自定义 setState
函数(数组第二个参数)的使用场景,实现 data 的自定义拼装
const [data, setData] = useState({}); setData((oldData)=>{ // ... return {...oldData, newKey: newValue}; // 实现 data 的自定义拼装 })
React 中如何使用类 componentDidMount 及如何取到 useEffect 中被监听变量的旧值?
mounted
const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []);
定义一个记录旧值的 hook usePrevious
// 参考引用:https://stackoverflow.com/questions/53446020/how-to-compare-oldvalues-and-newvalues-on-react-hooks-useeffect import { useRef, useEffect } from 'react'; const usePrevious = (value) => { const ref = useRef(); useEffect(() => { ref.current = value; }); return ref.current; }; export default usePrevious;
使用 usePrevious
用来记录旧值
// ... import { usePrevious } from '$myKooks'; // ... const [val, setVal] = useState(initialVal); const prevVal = usePrevious(val); useEffect(() => { if (mounted && prevVal !== val) { // 处理自定义事件 } }, [val]);
React 中如何使用 React.createContext 进行组件之间由外到内单向传值?
React.createContext
主要是解决以前爷->父->子->孙组件如果通过 props
传值比较恶心的问题,可以一步到位(当然:现在还可以使用 useContext
这个 hook 一步到位取到值,可参考:https://reactjs.org/docs/hooks-reference.html#usecontext)。两种代码分别如下:父组件:
import React from 'react'; import Son from './son'; // 引入子组件 // 创建一个 theme Context, export const {Provider,Consumer} = React.createContext("默认名称"); const Parent = (props) => { let name ="小人头" return ( // Provider 共享容器接收一个 name 属性,并可以向下 Son 子组件传递 <Provider value={name}> <div style={{border:'1px solid red',width:'30%',margin:'50px auto',textAlign:'center'}}> <p>父组件定义的值:{name}</p> <Son /> </div> </Provider> ); } export default Parent;
子组件:
import React from 'react'; import { Consumer } from './Parent'; // 引入父组件的 Consumer 容器 import Grandson from './grandson; // 引入子组件 const Son = (props) => { return ( // Consumer 容器,可以拿到上文传递下来的 name 属性, 并可以展示对应的值,并且往孙也传递了下去 <Consumer> {( name ) => <div style={{ border: '1px solid blue', width: '60%', margin: '20px auto', textAlign: 'center' }}> <p>子组件。获取父组件的值:{name}</p> {/* 孙组件内容 */} <Grandson /> </div> } </Consumer> ); } export default Son;
孙组件:
import React from 'react'; import { Consumer } from './Parent'; // 引入父组件的 Consumer 容器 const Grandson = (props) => { return ( // Consumer 容器,可以拿到上文传递下来的 name 属性,并可以展示对应的值 <Consumer> {(name ) => <div style={{border:'1px solid green',width:'60%',margin:'50px auto',textAlign:'center'}}> <p>孙组件。获取传递下来的值:{name}</p> </div> } </Consumer> ); } export default Grandson;
使用 useContext
方式:
const themes = { light: { foreground: "#000000", background: "#eeeeee" }, dark: { foreground: "#ffffff", background: "#222222" } }; const ThemeContext = React.createContext(themes.light); // 暴露到外部一步到位的 useContext 方式 export const useThemeContext = () => { return React.useContext(ThemeContext) || {}; }; function App() { return ( <ThemeContext.Provider value={themes.dark}> <Toolbar /> </ThemeContext.Provider> ); } function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } function ThemedButton() { // const theme = React.useContext(ThemeContext); const theme = useThemeContext(); // 一步到位的 useContext 方式 return ( <button style={{ background: theme.background, color: theme.foreground }}> I am styled by theme context! </button> ); }
React 版 Ant Design 3.x 的那些坑 1.Popconfirm
Popconfirm
里不能隔一层 <>
会导致点击失效
<Popconfirm title="确认删除吗?" onConfirm={(e) => { // 删除 }} > <> <div> 点我试试 </div> </> </Popconfirm>
React 版 Ant Design 3.x 的那些坑 2.Form.Item 里的 getFieldDecorator
getFieldDecorator
里不能隔一层 div
或者其他,会导致取值失败,错误如下
<Form.Item label="Title"> {getFieldDecorator('title', { rules: [{ required: true, message: 'Please input the title of collection!' }], })( <div> <Input /> </div> )} </Form.Item>
熟悉 React 顶层 API,如 cloneElement isValidElement React.Children 等待
// 验证对象是否为 React 元素,返回值为 true 或 false。 React.isValidElement(object) // 以 element 元素为样板克隆并返回新的 React 元素。config 中应包含新的 props,key 或 ref。 // 返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。新的子元素将取代现有 // 的子元素,如果在 config 中未出现 key 或 ref,那么原始元素的 key 和 ref 将被保留。 React.cloneElement( element, [config], [...children] ) // 将 children 这个复杂的数据结构以数组的方式扁平展开并返回,并为每个子节点分配一个 key。 // 当你想要在渲染函数中操作子节点的集合时,它会非常实用,特别是当你想要在向下 // 传递 this.props.children 之前对内容重新排序或获取子集时。 React.Children.toArray(children)
react 兼容 IE9 +
1.安装 react-app-polyfill 和 core-js
npm install react-app-polyfill core-js
2.index.js 引用:
import 'core-js/es' import 'react-app-polyfill/ie9' import 'react-app-polyfill/stable'
3.修改package.json
"browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all", "ie > 9" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version", "ie > 9" ] }, "devDependencies": {}
一般做到这里 就可以了, 但是有些电脑IE文档模式 默认值是 7,
通过 F12—>仿真—>文档模式 进行查看; 这个时候需要在打包好的 index.html 或者 在 public 文件夹下的 index.html head里 加上一行:
<meta http-equiv="X-UA-Compatible" content="IE=edge">
React 页面数据守卫(路由、浏览器关闭、浏览器崩溃)
react
自带组件 Prompt
import { Prompt } from 'react-router-dom';
<Prompt when={yourBooleanCondition} message={() => '退出页面未保存,会导致正在编辑的数据丢失'} />
2、数据未保存,如何防止浏览器关闭?使用 beforeunload
监听
window.addEventListener('beforeunload', listenerCallback);
3、数据未保存,浏览器崩溃如何恢复数据?使用 componentDidCatch
或者 window.onerror
即:componentDidCatch(error, info)
或 window.removeEventListener('error', listenerCallback);
要注意策略:因为都不流行 class component
所以可以用 window.onerror
比较多。
1)先使用备份机制备份好数据(定时或者按需),量大可以备份到 sessionStorage
2)崩溃时(react
崩溃时可通过延时判断 #root
下元素有没有 mount
成功,即 Dom
内不为空),数据转存到 localStorage
。
3)下次打开时,凭 localStorage 判断是否提示恢复数据。
Create React App 项目 eject 后常见配置问题
webpackbar
显示打包或者构建进度条,舒服1)安装,
yarn add webpackbar --dev
2)配置 webpack.config.js
// ... const WebpackBar = require('webpackbar'); // ... plugins: [ new WebpackBar(), // 使用 // Generates an `index.html` file with the <script> injected. // ... ]
2、使用 thread-loader
配置多线程打包
1)安装, yarn add thread-loader --dev
2)配置 webpack.config.js
// ... // Process application JS with Babel. // The preset includes JSX, Flow, TypeScript, and some ESnext features. { test: /\.(js|jsx|ts|tsx)$/, exclude: /node_modules/, // 排除了,可以优化打包速度 include: paths.appSrc, use: [ "thread-loader", { loader: require.resolve("babel-loader"), // ... } ] }, { test: /\.mjs$/, include: /node_modules/, type: "javascript/auto", },
3、安装 @babel/plugin-proposal-decorators
decorators 支持 @
修饰符的使用
1)修改 package.json
文件,增加
{ // ... "babel": { "presets": [ "react-app" ], "plugins": [ [ "@babel/plugin-proposal-decorators", { "legacy": true } ] ] } // ... }
2)修改或者增加 .eslintrc.js
文件
module.exports = { root: true, extends: ['react-app'], rules: { 'linebreak-style': [0, 'error', 'windows'], 'react/react-in-jsx-scope': 'off', 'react/jsx-filename-extension': [1, { extensions: ['.jsx', '.js'] }], // 解决 index.js 中不能使用 JSX 'max-len': ['warn', 10000], 'no-unused-vars': 'off', 'no-useless-escape': 'off', 'react-hooks/exhaustive-deps': 'off', 'import/no-anonymous-default-export': 'off', 'no-mixed-operators': 'off', 'array-callback-return': 'off', 'jsx-a11y/anchor-is-valid': 'off', 'no-restricted-globals': 'off', 'no-control-regex': 'off', }, parserOptions: { parser: 'babel-eslint', "ecmaFeatures": { "legacyDecorators": true // 主要是这个选项 } } };
3)配置 .env
文件(本地开发第个成员可再添加 .env.local
文件, 需 gitignore)与 env.js
,把必要环境参数暴露给浏览器使用, 可与 node
环境共享使用
.env
文件配置
# 接口可能使用的前缀 PRODUCTION_API_URL_PREFIX=http://www.production_xxx.com
env.js
文件配置
// ... function getClientEnvironment(publicUrl) { const raw = Object.keys(process.env) .filter((key) => REACT_APP.test(key)) .reduce( (env, key) => { env[key] = process.env[key]; return env; }, { // Useful for determining whether we’re running in production mode. // Most importantly, it switches React into the correct mode. NODE_ENV: process.env.NODE_ENV || "development", // Useful for resolving the correct path to static assets in `public`. // For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />. // This should only be used as an escape hatch. Normally you would put // images into the `src` and `import` them in code to get their paths. PUBLIC_URL: publicUrl, // We support configuring the sockjs pathname during development. // These settings let a developer run multiple simultaneous projects. // They are used as the connection `hostname`, `pathname` and `port` // in webpackHotDevClient. They are used as the `sockHost`, `sockPath` // and `sockPort` options in webpack-dev-server. WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST, WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH, WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT, // Whether or not react-refresh is enabled. // react-refresh is not 100% stable at this time, // which is why it's disabled by default. // It is defined here so it is available in the webpackHotDevClient. FAST_REFRESH: process.env.FAST_REFRESH !== "false", PRODUCTION_API_URL_PREFIX: process.env.PRODUCTION_API_URL_PREFIX, // 添加这里,浏览器可能通过 process.env.PRODUCTION_API_URL_PREFIX 取到值 } ); // ...
使用范例
// ... const service = axios.create({ baseURL: process.env.NODE_ENV === 'development' ? '/' : process.env.PRODUCTION_API_URL_PREFIX, // api 的 base_url timeout: 30000, method: "post", credentials: 'include', withCredentials: true, headers: { "Content-type": "application/json; charset=utf-8", }, }); // ...
4)配置 husky 与 lint-staged 结合 eslint, stylelint, prettier 规范提交代码
pre-commit
文件
#!/bin/sh . "$(dirname "$0")/_/husky.sh" yarn lint-staged # 按需,可能不是 yarn
.lintstagedrc.js
文件
// .lintstagedrc.js 文件中的代码如下 module.exports = { "src/**/\*{js,jsx,ts,tsx,html}": ["eslint", "prettier --write"], "src/**/\*{.scss,.less,.css}": ["stylelint --fix"], }
详细可参考:https://www.jianshu.com/p/a7cea983e7a2
5)配置 .editorconfig
.eslintrc.js
.prettierrc
.prettierignore
.stylelintrc.json
.yarnrc.yml
src/setupProxy.js
.lintstagedrc.js
.env
.env.local
.gitignore
package.json
docker 目录
.dockerignore
文件
credentials,withCredentials 接口请求配置
fetch
/ axios
中加上 credentials: 'include'
反而会报错 跨域问题当
Access-Control-Allow-Origin
的值为 '*'
的时候跨域请求不允许携带认证那么只能在服务端修改
Access-Control-Allow-Origin
的值,改成你指定的允许跨域访问的域的值
axios
跨域 withCredentials
设置与后段设置
axios.defaults.withCredentials = true
在请求头里带上 cookie
如果前端配置了这个 withCredentials = true
,后段设置 Access-Control-Allow-Origin
不能为 '*'
, 必须是源地址。如:
header("Access-Control-Allow-Origin", "源地址");
header("Access-Control-Allow-Credentials", "true");
Webpack 中编写自定义 loader 和 plugin, 及如何调试 (debug) webpack, create-react-app 等脚手架
调试分为两种:
浏览器中调试
vscode中调试
如 create-react-app 中的 package.json 配置 scripts 启动调试命令:
"debug-webpack": "node --inspect-brk ./node_modules/webpack/bin/webpack.js --config webpack.config.js"
更多可参考:https://blog.csdn.net/fesfsefgs/article/details/119983556
React 中的 useSelector 怎么用?useSelector 取出的值永远是旧值,不更新怎么处理
这个 hook 超好用,如果有用过 vuex 的 getters
, 大概就这个意思,就是去统一的 state 里按需取数,如下,可以直接取 item1 来用了
const { list, item1, } = useSelector(state => ({ list: state.list.info, item1: state.list.find(item => item.id === 'id1'), }));
useSelector
取出的值永远是旧值,不更新怎么处理?使用 useLayoutEffect
结合
// 接着上面的代码 const listRef = useRef(list); useLayoutEffect(() => { listRef.current = list; }, [list]); // 下面使用 listRef.current 就可以取到最新的值了
React 中的 useState,组件深层次使用时,值为数组类型 或 对象,更新内部某孙值时,Dom UI 不更新怎么处理?
这画面似层相识对吧?没错,vue 2 使用数据类型为数组时也会有类似的情况,react 其实也会有类似的情况。
【方式一】处理方式其实比较简单。我们更新数据时,换一个数组 / 对象吧。代码如下:
// 定义处 const [arr, setArr] = useState([]); const [obj, setObj] = useState({}); // 更新处 arr[1].x = 1; setArr([...arr]); // 换一个数组吧 obj.x = 1; setObj({...obj}); // 换一个对象吧
【方式二】上面方式还是不行?
使用延时渲染,思路类似如下:
setTableDataSource([]); // 先清空,如 antd 的 Table 绑定了 tableDataSource setLoading(true); // 结合 loading 体验好点 setTimeout(() => { setTableDataSource(myDataSource); // 延时再赋值 setLoading(false); }, 500);
交流与学习
本文作者:Nelson Kuang,欢迎大家留言及多多指教
版权声明:欢迎转载学习 => 请标注信息来源于 http://www.kt5.cn/fe/2021/08/26/react-ii/