React和ThinkPHP使用中踩到的坑集中在这篇文章中处理
报错: React: Maximum update depth exceeded error
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
handleModalVisible = (flag,type) => { if(type === 'passModal'){ this.setState({ pswModalVisible:!!flag }) } }; ... <a onClick={this.handleModalVisible(true,'passModal')}>xxx</a> // 这是错的,每次render()的时候都会执行 ... ... <a onClick={()=>this.handleModalVisible(true,'passModal')}>xxx</a> //这样写就好了 ... |
这是因为在render方法里面this.xxx()相当于执行这个函数,也就是说每一次render的时候都会重复的执行这个函数,为了防止不断重复的执行这个函数需要将onclick放入到一个箭头函数中.
TP中自动拼合URL
再数据库存储图片的时候如果图片是上传到自己的服务器中的一般不直接写完整的url而是部分url,这个时候再取出数据的时候就需要加上URL的前缀。
首先要创建一个模型的基类,Basemodel来提供拼接的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php /** * Created by bingxiong. * Date: 10/20/18 * Time: 3:35 PM * Description: */ namespace app\api\model; use think\Model; use think\facade\Config; class BaseModel extends Model { protected function prefixAvatarUrl($value,$data){ $finalUrl = Config::get('setting.avatar_prefix').$value; return $finalUrl; } } |
然后模型继承这个基类后要对该方法进行命名,这里的方法命名是由要求的,必须是”get+字段名+Attr“,这里要改变前缀的字段名是avatar,因此就是getAvatarAttr,这个方法会由两个参数一个是value,这是该字段的数据,还有一个数据是data,这是所有查出来的数据,可以根据data里面的数据来改变字段的前缀,比如说再data中还有一个from指明图片的来源是完整的地址还是需要进行拼接的地址,这里直接全部都拼接上去,因为全部都是需要拼接的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<?php /** * Created by PhpStorm. * User: xiong * Date: 2/25/2019 * Time: 2:04 PM */ namespace app\api\model\Account; use app\api\model\BaseModel; class Profile extends BaseModel { protected $hidden = ['id']; /**指定拼接Avatar的URL * @param $value 其他查询出的数据 * @param $data Avatar * @return string */ public function getAvatarAttr($value,$data){ return $this->prefixAvatarUrl($value,$data); } } |
Ant Design Pro 自定义IconFont
自定义Icon的方法文档里面有,这里主要讲一下自定义侧边栏
首先创建一个IconFont的类
1 2 3 4 5 6 7 |
import { Icon } from 'antd'; const IconFont = Icon.createFromIconfontCN({ scriptUrl: '//at.alicdn.com/t/font_690913_zyib6a0a8m.js' }); export default IconFont; |
然后要使用直接引入这个类就可以了
1 2 3 |
<Card bordered={false}> <IconFont type="icon-shujuliuliangjiance" style={{ fontSize: '16px', color: 'lightblue' }} /> </Card> |
如果修改侧边栏的Icon,那么再components下找到SideMenu/BaseMenu.js引入后直接用刚刚创建的IconFont这个类覆盖掉原来从AntD UI库中引入的Icon即可
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import IconFont from '@/components/IconFont'; const { SubMenu } = Menu; const getIcon = icon => { if (typeof icon === 'string' && isUrl(icon)) { return <img src={icon} alt="icon" className={styles.icon} />; } if (typeof icon === 'string') { return <IconFont type={icon} />; } return icon; }; |
其实ant design再iconfont上面也有图标库https://www.iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.d9df05512&cid=9402
Modal回填的正确使用方式
之前的带表单回填功能的弹出层一直有一点问题看,在取消的时候是突然消失,没有关闭的动画效果
1 2 3 4 5 6 7 |
{record ? ( <ConfigModal visible={configModalVisible} handleCancel={()=>{this.handleModalVisible(false,'config')}} record={record} /> ):null} |
这里的record就是要回填到表单的数据传给子组件,写成这样的形式是为了防止在主题页面渲染的时候就渲染子组件的内容,因此判断了一下,在用户没有点击编辑的时候不要渲染modal,注意这里返回的是null,因此如果直接在子组件关闭的时候直接删掉record的话就会导致主modal被直接销毁也就没有关闭动画了,解决这个问题的方法是在子组件关闭的时候先把record传回来,然后在完全关闭了之后的回调中再去把record置空:
在子组件中:onCancel的时候将record将record传回,然后在afterClose中将record置为空
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
render() { const { visible, handleCancel,record } = this.props; const {deviceSelectDrawVisible, alarmTemplateVisible, contactVisible} = this.state; return ( <div> <Modal width={640} bodyStyle={{ padding: '32px 40px 48px' }} visible={visible} footer={this.renderFooter()} title="新增告警" onCancel={()=>{handleCancel(false,'config',record)}} afterClose={()=>{handleCancel(false,'config',{})}} destroyOnClose > {this.renderContent()} </Modal> </div> ); } |
使用这种方式的两个优点:
- 用户不点击的时候不会渲染modal的内容,提升了前端的性能
- 取消的时候仍然有取消的动画效果
react的setState在循环中无效
由于setState是异步的,因此不能在循环中重复的setState,正确的方法应该是用一个临时的变量先存储则,循环完毕后再循环的外面再setState。
Promise 同步处理ant design照片墙移除功能
由于照片墙在移除的时候需要用户二次确认,这里就需要使用到onRemove(file),然后在这个方法中进行弹窗询问,如果这个方法返回false的时候就不会删除掉(前端删除),然而问题是弹窗是异步的,也就是还没有到用户点击就直接走到了后面的,无法返回弹窗中的false,因此就需要使用到promise来模拟同步的进行。
参考来源 http://es6.ruanyifeng.com/#docs/promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
onRemove = (file) => { console.log(file); const { templateId } = this.props; const {url} = file; const payload = { templateId, url }; return this.showDeleteConfirm(payload); }; showDeleteConfirm = (payload) => { const {dispatch} = this.props; const promise = new Promise((resolve,reject)=>{ confirm({ title: '您确定要删除该图片吗?', content: '将删除该标签组所有标签中的该图片', okText: '删除', okType: 'danger', cancelText: '取消', onOk() { // 在这里请求删除接口 dispatch({ type: 'tag/deleteTagGroupImg', payload, callback: (response) => { console.log(response); if(response.code){ message.success('删除成功'); resolve(); }else{ message.warning('删除失败'); reject(); } }, }); }, onCancel() { reject(); }, }); }); return promise; }; |
在上传的过程中不仅仅是这里要使用promise在beforeUpload里面也要使用这个方法来模拟同步的进行。
参考来源:http://react-component.github.io/upload/examples/beforeUpload.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
beforeUpload = (file) => { return new Promise((resolve, reject) => { console.log('start check'); const isJPG = file.type === 'image/jpeg'; if (!isJPG) { message.error('图片格式必须是jpg或png'); reject(); } const isPNG = file.type === 'image/png'; if (!isPNG) { message.error('图片格式必须是jpg或png'); reject(); } const isLt2M = file.size / 1024 / 1024 < 2; if (!isLt2M) { message.error('文件必须小于2MB'); reject(); } resolve(file); }); }; |
这是一个库踩的坑,这是一个PHP生成qrcode的库endroid/qr-code
生成二维码
1 2 3 4 5 6 7 8 9 10 11 |
public function downloadEditQrCode($tag_id){ $qrCode = new QrCode($tag_id); // www.feifei50.com/../tag?action=edit $qrCode->setMargin(10); $qrCode->setErrorCorrectionLevel(new ErrorCorrectionLevel(ErrorCorrectionLevel::HIGH)); $qrCode->setForegroundColor(['r' => 0, 'g' => 0, 'b' => 0, 'a' => 0]); $qrCode->setBackgroundColor(['r' => 255, 'g' => 255, 'b' => 255, 'a' => 0]); $qrCode->setLogoPath('../public/static/logo/FeiFeiLogo.png'); $qrCode->setLogoSize(80, 80); $qrCode->writeFile('../public/qrcode/'.$tag_id.'edit.png'); return download('../public/qrcode/'.$tag_id.'edit.png','qrcode'.$tag_id); } |
特别要注意的是这个库的writeFile方法,如果没有前面的文件夹是不能够写入文件的。
Ant Design Pro上线采坑
昨天将新版的飞飞物联上线到生产环境测试,不像普通的使用react-router的项目,打包之后index.html就直接就可以运行了,由于使用了“强大的”uim以及自带的mock,使得上线的过程并不是那么的顺利,要成功的上线需要注意几点,参考uim的文档:https://umijs.org/zh/guide/deploy.html
- mockjs在线上仍然会生效,依旧会请求例如api/auth_routes(服务器动态权限),由于线上打包好的环境已经没有了mockjs,因此这个请求这个接口,但这个接口服务器是没有实现的,所以这个时候如果请求的话请求的是一个文件,但是我们没有这个文件,就会走到404页面,目前解决的方法是在dist根目录出创建这个文件api/auth_routes然后把mockjs里面的数据放进去,github上也有issue说这个问题,这个接口严重的影响了上线流程。
- 另一个坑是umi默认的是history是browser,这样就会在每个路由挑战的时候去请求服务器,方式由于前后端是完全分离的,前端通过请求后端的API来获取数据,因此不能够使用这种方式,需要在config/config.js中的export加上,然后才能够让前端完全控制路由。
1 |
history: 'hash', |
ThinkPHP使用Redis采坑
其实直接使用predis单例模式调用就可以了,但其实TP自带了redis,但要注意不是cache的那个redis,TP自带的redis在
1 |
use think\cache\driver\Redis |
引用这个类重写protected方法连接上即可,那个cahce的redis是缓存用的,TP的redis很多方法是没有的,需要自己重写一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?php /** * Created by bingxiong. * Date: 4/5/19 * Time: 3:50 AM * Description: */ namespace app\lib\redis; use think\cache\driver\Redis as ThinkphpRedis; class Redis extends ThinkphpRedis { protected $options = [ 'host' => '127.0.0.1', 'port' => 6380, 'password' => '', 'select' => 0, 'timeout' => 0, 'expire' => 0, 'persistent' => false, 'prefix' => '', 'serialize' => true, ]; public function test($index, $key, $value){ $this->handler->hSet($index, $key, $value); } } |
宝塔面板部署TP所有路由访问全部nginx 404
之前在宝塔面板部署TP项目都是使用一键部署,再把部署的源代码删掉上传自己代码,这样就不需要配置nginx就可以直接访问了,之前至少这样成功部署过十几次都没有问题发生过,但是就在宝塔面板升级了之后使用同样的方法部署自己项目所有路由访问全部都是nginx-404,奇怪的时候线上调试的时候确定代码已经走到了TP框架的逻辑中,按道理来说如果走到了TP的逻辑即使有错也应该是TP报错而不是nginx报错,后来查看一天之后发现宝塔新版部署的TP伪静态有问题,需要将它的伪静态删除然后替换成如下,真的是坑死我了
1 2 3 4 5 6 7 8 9 10 11 12 |
#禁止执行PHP的目录 location ~ ^/(thinkphp|vendor/phpunit|application|runtime)/.*\.php { return 404; } location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s=$1 last; break; } } |
React Empty Response
这个坑真的是坑死我了,回国后发现react的请求全部报这个错误,但是使用psotman全部都可以访问,后来发现是VPN不能使用global mode,要选择bypasschina, 醉了,浪费了一个早上.
ThinkPHP查询时候添加额外字段
1 2 3 4 5 6 7 |
$positions = CameraModel::where('project_id', $this->project_id) ->where('status','=',1) ->where("position",'<>',"") // ->field('position, camera_id, title') ->field(['position','camera_id'=>'device_id','title'=>'device_name','\'camera\'' => 'type']) ->json(['position']) ->select(); |
TP是没有添加额外字段的方法的,可以巧妙的利用转换成SQL的as来进行添加