1. es6扩展运算符将组建嵌套的dom渲染到组件内部
查看react-component/dialog的时候发现了直接使用...props将组件的嵌套标签渲染到组件内部的情况,做了如下实验。
PaperItem.tsx
import * as React from "react";
export interface Props {
name: string;
enthusiasmLevel?: number;
onIncrement?: () => void;
onDecrement?: () => void;
}
export default function PaperItem(props: Props) {
return <div className="paper-item" {...props} />;//使用扩展运算符
}
PaperList.tsx
import * as React from "react";
import PaperItem from "../components/PaperItem";
export default class PaperList extends React.Component {
data = {
name: 99
};
render() {
return (
<div>
{[1, 2, 3, 4].map(x =>
<PaperItem key={x} name={String(x)}>
<h1>ii</h1>//将这两行插入到PaperItem里面
<h2>oo</h2>
</PaperItem>
)}
</div>
);
}
}
渲染结果:
name props会渲染到展开他的标签上,而children就作为该标签的子节点渲染出来了,通常是使用tihs.props.children拿到组件里面嵌套的标签,然后遍历出来,这么写的确是一条捷径。
vue有slot可以轻松做到嵌套的dom,渲染到组件内部,命名slot的占位作用可以准确的定位要插入的位置和数量,react这边有待发现。
昨晚查阅了react官方文档,在组合 vs 继承章节,详细说明了props.children的使用和类似vue slot的使用,类似slot的占位和多个外部dom或者组件片段插入组件,react是通过props实现的,props可以传递基础数据类型,函数,原生的dom,或者组件。这样就很方便了。
2. 组件类型
- 1.类组件合函数组件(无状态函数组件)
在官网组件 & Props提到了累组件和函数组件,函数组件没有state没有生命周期函数,所以每次更新都是由外面传入的props的改变引起的,而且渲染速度比类组件要快。一般函数组件作为展示组件使用,展示数据和效果,状态的改变交给外部的类组件,对于redux来说就是容器组件,在react定义d.ts接口的时候定义了如下的接口:
type SFC<P = {}> = StatelessComponent<P>;
interface StatelessComponent<P = {}> {
(props: P & { children?: ReactNode }, context?: any): ReactElement<any> | null;
propTypes?: ValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
interface ComponentClass<P = {}> {
new (props?: P, context?: any): Component<P, ComponentState>;
propTypes?: ValidationMap<P>;
contextTypes?: ValidationMap<any>;
childContextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
interface ClassicComponentClass<P = {}> extends ComponentClass<P> {
new (props?: P, context?: any): ClassicComponent<P, ComponentState>;
getDefaultProps?(): P;
}
所以在定义一个函数类的时候可以去这么定义:
import * as React from 'react'
export interface LockPropType {
src: any
onClick?: (e?: any) => void
}
const Lock: React.StatelessComponent<LockPropType> = ({ src, ...restProps }) => {
return (
<div>
</div>
)
}
export default Lock
3. 等待state更新完后,利用更新后的state值去做其他操作
项目里每个有的页面是公用的,不同的路由有公用一个组件。这个时候路由的props match改变了所以走了componentWillReceiveProps钩子函数,这时候需要初始化页面的state,并利用初始化后的参数做ajax请求。那么就可以使用setState(updater, [callback])
this.setState({name:'geek'},()=>{
request('url',{name:this.state.name}).then()
})
在react-component有详细说明
2. Children.only(this.props.children)
在读react-redux/src/components/Provider.js源码的时候遇到了这么一句话
class Provider extends Component {
render() {
return Children.only(this.props.children)
}
}
然后结合实际的项目看
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './components/App'
import reducer from './reducers'
const store = createStore(reducer)
render(
<Provider store={store}>
<App />//那就是Provider组件只能包含一个根子组件,也就是一个对象,而不是数组
</Provider>,
document.getElementById('root')
)
React.Children是react顶层的api其他文档可参考React 顶层 API
3. 使用vscode调试create-react-app
VSCode debugging with Create-React-App
4. react拖拽生成组件代码
5. redux-devtools 配置
- 不安装Chrome redux devtools
使用 Redux-devTools简单的使用 这种方式会在页面生成redux devtools - 安装Chrome redux devtools 那么默认就使用Chrome redux devtools
store = createStore(
combineReducers(reducers),
compose(applyMiddleware(thunk),window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()) //插件调试,未安装会报错
);
6. antd table 设置固定在顶部 不对齐的问题
table 设置 scroll.y 后 theader 和 tbody 的 border 无法对齐
able align broken when cell contains long number or long word after 3.11.3
解决办法是
columns={[
...
render: (text, record) => (
<div style={{ wordWrap: 'break-word', wordBreak: 'break-all' }}>
{text}
</div>
),
]}
但是还是不能完全解决问题,有时候会提前内容截断
7. antd 事件触发报错
rning: This synthetic event is reused for performance reasons. If you're seeing this,
<a
onClick={this.handleDeleteConfirm("deleteRow")}
data-key={record.key}
>
{t("delete")}
</a>
handleDeleteConfirm = funcName => event => {
event.persist(); //使用这个就不报错了
Modal.confirm({
title: "Do you want to delete these items?",
onOk: () => {
// console.info(event);
this[funcName](event);
},
onCancel() {}
});
};
不使用 event.persist()的时候 event.target 居然是confirm的确定按钮,按理说是a标签才对,event.persist()的作用通过在回调事件顶部加上 e.persist() 就可以从池中移除合成事件,并允许对事件的引用保留。
8. create-react-app@3 运行jest测试报错
首先项目是经过eject,所有的配置都被暴露出来,项目中是使用Absolute Imports的方式引入包
jsconfig.json配置
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}
然后直接import Button from 'components/Button';
不需要用相对路径Absolute Imports,但是在用jest做测试的时候就报错 cannot find module
需要在package.json的jest配置项中配置如下参数
moduleDirectories: ['node_modules', 'src']
Jest + Typescript + Absolute paths (baseUrl) gives error: Cannot find module
9. create-react-app@3 注册全局函数
setupTests.js
import React from "react";
import { configure, shallow, render, mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });
global.React = React;
global.shallow = shallow;
global.render = render;
global.mount = mount;
在package.json中jest选项配置
"setupFilesAfterEnv": [
"<rootDir>/src/setupTests.js"
]
这样在测试文件中不需要引入react跟enzyme等文件,直接使用
import Temperature from "./Temperature";
it("renders correctly", () => {
const wrapper = shallow(
<Temperature temp={10} city="Toronto" toggleForecast={() => {}} />
);
expect(wrapper).toMatchSnapshot();
});
使用 Jest 和 Enzyme 测试 React 组件
Testing React with Jest, Enzyme, and Sinon
Using enzyme with Jest
但是commit的时候通不过eslint的检测,
- 对于
error 'shallow' is not defined no-undef
参考了 Solving linter error- 'shallow' is not defined no-undef在package.json设置加入
{
"globals": {
"shallow": true,
"render": true,
"mount": true
}
}
- 针对
error 'React' must be in scope when using JSX react/react-in-jsx-scope
参考How to use ESLint with Jest 安装eslint-plugin-jest,然后配置
"overrides": [
{
"files": [
"**/*.test.js"
],
"env": {
"jest": true
},
"plugins": [
"jest"
],
"rules": {
"react/react-in-jsx-scope": "off"
}
}
],
- css modules 下覆盖antd的样式
then.less
.stopRuleSet {
.ant-radio {
display: none !important;
}
}
然后radio的样式并没有生效,编译为
.then_stopRuleSet__35L3K .then_ant-radio__oQkAO {
display: none !important;
}
.ant-radio的样式被也被加了hash值,标签里的classname没加hash
所以less文件中的classname也不应该被加上hash值,加上:global就好了
.stopRuleSet {
:global {
.ant-radio {
display: none !important;
}
}
}
10. react-router 获取上一次路径
vue-router的beforeRouteEnter beforeRouteUpdate beforeRouteLeave 导航守卫的from参数是上个路由的。详见导航守卫
但是react-router@4 是没有这些钩子函数的。Detect previous path in react router? 给了我们一些方式
- You can pass down state using the <Link> component, in this case a pathname:
<Link to={{pathname: '/nextpath', state: { prevPath: location.pathname }}}>Example Link</Link>
You can then access prevPath from this.props.location.state in the next component
直接用push传递state也可以
- If you're using react-router-redux you can create a reducer which hooks into the events dispatched by react-router-redux.
export default function routerLocations(state = [], action) {
switch (action.type) {
case "@@router/LOCATION_CHANGE":
return [...state, action.payload]
default:
return state;
}
}
自定义reducer在路由跳转触发dispatch抛出的@@router/LOCATION_CHANGE action,记录每次路由的变化
- 再或者使用第三方库react-router-last-location 该库也支持hooks写法