树莓派固件更新 rpi-update

1 简介

rpi-update是一个用于更新树莓派固件的工具,可以通过apt get install rpi-update来安装

一般来说直接执行下面的命令就可以更新固件(扯淡,基本不可能好吗):

sudo rpi-update

2 跳过自更新

rpi-update启动时会更新自己,如果报以下错误:

!!! Failed to download update for rpi-update! 
!!! Make sure you have ca-certificates installed and that the time is set correctly

可以尝试一下他说的方法来解决:

#安装CA证书(感谢评论区指正)
sudo apt-get install ca-certificates

#同步时间
sudo apt-get install ntpdate
sudo ntpdate -u ntp.ubuntu.com

如果都不行,直接跳过算了,反正也可以用apt来更新这个工具,没必要让他更新自己。

跳过自更新,直接更新固件的方法:

sudo UPDATE_SELF=0 rpi-update

3 【究极方法】本地更新

即使跳过自更新,下载速度也太慢,还经常断流。可以考虑本地更新:

先在PC上下载固件(可以用一下魔法上网):

curl -L https://github.com/Hexxeh/rpi-firmware/archive/master.tar.gz -o master.tar.gz

然后用scp传到树莓派上的/root目录下,之后ssh连上树莓派:

# 切换到root用户(第一次切到root记得用sudo passwd root激活)
su

# 进入.rpi-firmware目录并解压(如果没有该目录,就创建一个)
mkdir /root/.rpi-firmware
cd /root/.rpi-firmware && tar -xvzf /root/master.tar.gz

# 【注】这一步是我自己加的,可能是遇到了特殊情况。
# 检查一下当前目录中是否有*.elf文件,如果没有,就说明那些文件
# 在当前目录下的一个叫rpi-firmware-master的子目录下
# 把里面的文件全拷贝到当前目录中(/root/.rpi-firmware)
cp -r ./rpi-firmware-master/* ./

# 执行本地更新
UPDATE_SELF=0 SKIP_DOWNLOAD=1 rpi-update

# 重启
reboot

4 检查

对于树莓派4的一个检查方法:

ls -la /opt/vc/lib

看该目录下是否有libEGL.solibGLESv2.so这两个库,更新前这两个库都是没有的。

如果这两个库出现了,并且是真正的库而不是软链接,就说明更新大概可能也许成功了吧。

参考链接


树莓派固件更新(rpi-update)的那些坑

群晖DSM7通过Docker安装WordPress最新版

PHP 7.x连接MySQL 8.x报错“The server requested authentication method unknown to the client [caching_sha2_password]”

PHP 7.x使用如下代码连接MySQL 8.x

<?php
mysqli_connect('localhost:3306','user','password','database') or die('Error connecting to MySQL server.')
?>

结果出现如下错误信息

Warning: mysqli_connect(): The server requested authentication method unknown to the client [caching_sha2_password] in xx.php

原因为 MySQL 支持 caching_sha2_password  / 用户名密码 两种验证方式,但是从 MySQL 8 开始默认使用caching_sha2_password 的验证方式,导致以前版本的客户端不能成功连接。更详细的解释如下:

The server validates the user and returns the connection status. MySQL uses caching_sha2_password and auth_socket plugins for validation.

The caching_sha2_password plugin uses an SHA-2 algorithm with 256-bit password encryption. MySQL 8 prefers this auth method.

Whereas the auth_socket plugin checks if the socket username matches with the MySQL username. If the names don’t match, it checks for the socket username of the mysql.user. If a match is found, the plugin permits the connection.

But to serve the pre 8.0 clients and avoid compatibility errors, it is preferred to revert back the auth method. Older versions of MySQL use mysql_native_password plugin for validation.

解决方案为修改用户默认的认证方式,如下:

ALTER USER 'user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';

参考链接


[RESOLVED] MySQL the server requested authentication method unknown to the client

macOS Monterey (12.0.1)解决App Store无法下载APP的问题

最近,公司的一台电脑升级到 macOS Monterey (12.0.1),之后从进行软件更新,以及 XCode 的下载安装。由于网速的限制,临时把 XCode 的下载进行了暂停操作,之后忘记恢复就进行了重启。

结果第二天在App的安装界面上就看不到被暂停的任务了。然后在 App Store 中尝试继续进行下载,发现无法进行下载任务,下载按钮点击之后,状态一闪而过,又回到待下载状态。

更换账号/重启应用/重启系统之后,无法解决问题。

应该是 App Store 内部状态混乱了,导致无法进行下载任务,需要对 App Store 进行重置操作。

网上搜索很久,才找到解决方案,执行如下命令:

$ sudo find /private/var/folders/ -iname 'com.apple.appstore' 2>/dev/null | while IFS='' read -r line; do rm -fr "$line"; done; rm -fr ~/Library/Caches/*store*; ps aux | egrep -i 'app.?store' | awk '{ print $2 }' | while IFS='' read -r line; do kill -9 "$line"; done

之后重启系统,重新点击下载应用,即可解决问题。

参考链接


群晖DSM7通过Docker安装MySQL最新版(8.x)

搜索并下载镜像,如下:

注意:此处搜索MySQL,会出现两个MySQL镜像,很容易让人不知道该如何选择。其实这两个镜像一个是社区维护,一个是Oracle维护的版本。两者对于普通用户来说,差别不大,选择任意一个都可以。这里,我们选择社区维护的版本。

配置必要的参数信息:

存储位置配置如下:

存储位置(/var/lib/mysql)是镜像中MySQL的默认数据存储目录,位置(/etc/mysql/conf.d)是镜像中MySQL的配置文件目录,这两个目录位置不要变更,如果需要修改配置,则在主机的对应目录上进行调整。

MySQL 服务启动时会以 /etc/mysql/my.cnf 为配置文件,之后会导入 /etc/mysql/conf.d 目录中所有以 .cnf 为后缀的文件。这些文件会拓展或覆盖 /etc/mysql/my.cnf 文件中的配置。

因此可以在挂载至 MySQL 容器中的 /etc/mysql/conf.d 目录创建需要的配置文件。

比如如下:

[mysqld]
# 绑定地址禁止外网访问,安全性
bind-address = 127.0.0.1 

#设置server-id,必须唯一
server-id = 5

# 根据业务需要配置数据库只读
read_only = on
#super_read_only = on
#tx_read_only = on

log_bin = /var/logs/mysql/mysql-bin.log

relay-log = /var/logs/mysql/mysql-relay-bin

#如果日志开启了,最好把日志格式设置为row格式,这样如果主从数据不一致,可以尝试mysql flashback功能
binlog-format = row 

# 不同步哪些数据库  
binlog-ignore-db = mysql
  
binlog-ignore-db = test  

binlog-ignore-db = information_schema

# 缓冲区不足,可能会在还原数据的时候报错
# “ERROR 2006 (HY000) at line 241: MySQL server has gone away”
# 观察日志报告 Got a packet bigger than 'max_allowed_packet' bytes
# 这个缓冲区的大小必须大于或等于主库上设置的大小
max_allowed_packet = 256M

# 对于WordPress的情况,我们可能需要兼容低于 MySQL 5.x的数据库
sql_mode=ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

对于MySQL的日志目录,需要进行特殊授权,否则没办法启动成功。

权限授权方式如下:

配置MySQL的默认登陆密码:

MYSQL_ROOT_PASSWORD 为手工添加,其它是安装 MySQL 自带的。MYSQL_ROOT_PASSWORD 用于设置 root 的密码。

查看系统运行状态:

设置登录的SHELL

如果想替换掉群晖自带的MariaDB,实现主从同步,可以参考家里ADSL上网无固定外网IP的群晖NAS安全实现与公网MySQL服务器主从同步

$ mysql -u root -p -e "CHANGE MASTER TO MASTER_HOST='10.8.0.1',MASTER_USER='repl1',MASTER_PASSWORD='slavepass',MASTER_LOG_FILE='mysql-bin.001558',MASTER_LOG_POS=8071938,master_ssl=1,master_ssl_ca='/etc/mysql/conf.d/ssl/ca.pem',master_ssl_capath='/etc/mysql/conf.d/ssl',master_ssl_cert='/etc/mysql/conf.d/ssl/client-cert.pem',master_ssl_key='/etc/mysql/conf.d/ssl/client-key.pem';"

默认Docker创建出来的MySQL是外部网络都可以访问的,这样不安全,我们可以通过群晖自带的防火墙禁止特定的端口访问来阻止外部IP的访问请求。

操作步骤如下:

参考链接


从Vue 2到Vue 3组织代码的思维转变

到了Vue 3,当我们看完组合式 API相关文档,蠢蠢欲动立马上手把业务代码挪到setup内时,竟发现无法在setup()内部通过 this 获取当前组件实例了(this 是 undefined)!

其实想要变相获取“this”可以用 getCurrentInstance ,比如像这样访问全局属性:

import { getCurrentInstance } from 'vue'

const MyComponent = {
  setup() {
    const user = ref('aizawasayo')
    const internalInstance = getCurrentInstance()
    console.log(this) // undefined
    // 1. 访问 globalProperties
    console.log(internalInstance.appContext.config.globalProperties)
    // 2. 当前组件实例真正内容
    console.log(internalInstance.ctx, internalInstance.proxy)
    return {
      user
    }
  }
}

真正属于组件的内容是ctxproxyproxy就是对ctx包装了一层 Proxy。目前看来属实没用,就不展开来说了。

getCurrentInstance 只能setup生命周期钩子中调用。不仅使用起来麻烦,且只适用于开发环境。官方都告知我们日常开发中不要用:

貌似到头来this的问题还是无解。Vue 3 Composition API 的设计初衷也是为了减少对组件实例的依赖,避免this指向的困扰。包括一些全局方法也可以提取到 composables 组合函数中,无需再通过当前组件的原型链获得。我们动不动就通过this获取组件实例的思想该转变了

怎么个直接使用法?我们先看一眼简易版 选项式 API组合式 API 的对比。再用例子来捋:

API 对比

1. 读写数据

Vue 2的做法,或者说Option API更准确 (后面就简写成Vue 2)

data中定义,this.xxx获取
<template>模版中不需要用this

<script>
import { getUsers } from '@/api/user'
export default {
  name: 'User',
  data: {
    return {
      listLoading: false,
      list: [],
      total: 0,
      queryInfo: {
        query: '',
        page: 1,
        pageSize: 10
      },
      emptyText: '没有相关数据',
    }
  },
  mounted() {
    this.listLoading = true
    getUsers(this.queryInfo).then(response => {
      this.list = response.data.list
      this.total = response.data.total
      this.listLoading = false
    })
  }  
}
</script>
Vue 3 的 Composition API (后面就简写成 Vue 3)

ref reactive定义。
通过ref方法定义的属性在 setup 函数内需要通过.value去访问它的值 (template 模版内不用), reactive则不用。我们可以简单地把 ref(obj) 理解为 reactive({ value: obj })
详细请看 ➡️ 【Vue 3 之:弄清 ref reactive toRef toRefs】

如何选择 refreactive?建议:

  1. 基础类型值(StringNumberBoolean等) 或单值对象(类似{ count: 3 }这样只有一个属性值的对象) 使用 ref
  2. 引用类型值(ObjectArray)使用 reactive
  3. 对于 ref 对象可以使用 unref 语法糖来免去.value访问的困扰
<script>
import { ref, reactive, onMounted } from 'vue'
export default {
  name: 'User',
  setup() {
    const listLoading = ref(false)
    const list = ref([]),
    const total = ref(0),
    const queryInfo = reactive({
      query: '',
      page: 1,
      pageSize: 10
    })
    const emptyText = ref('没有相关数据')

    onMounted(() => {
      listLoading.value = true
      getUsers(queryInfo).then(response => {
        list.value = response.data.list
        total.value = response.data.total
        listLoading.value = false
      })  
    })

    return {
      listLoading, 
      list,
      total,
      queryInfo,
      emptyText
    }
  }
}
</script>

2. 定义/调用方法

Vue 2:methods

继续上面的例子,我们把请求列表数据的操作提取成一个方法。(略去重复的代码)

<!--模版中调用 methods 方法-->
<template>
  <button @click="show('🐸')">触发 show</button>
</template>
<script>
export default {
  methods: {
    async getList(params) { // 1.定义
      this.listLoading = true
      const response = await getUsers(params)
      this.list = response.data.list
      this.total = response.data.total
      this.listLoading = false
    },
    show(msg){
      console.log('helo', msg)
    }
  },
  mounted() {
    this.getList(this.queryInfo) // 2. 使用
  }  
}
</script>
Vue 3
<script>
import { onMounted } from 'vue'
export default {
  setup() {
    const getList = async (params) => { // 1.定义
      listLoading.value = true
      const response = await getUsers(params)
      list.value = response.data.list
      total.value = response.data.total
      listLoading.value = false
    }

    onMounted(() => {
      getList(queryInfo) // 2. 使用
    })
  }
}
</script>

3. 获取模版DOM元素/或组件实例的引用

Vue 2

直接整上 element-plus Form 表单 的的例子,<el-form>是我们写的DemoForm组件的一个子组件,通过为它添加一个refattribute,就能使用使用this.$refs[ref值]直接访问子组件以及它的所有属性/方法了。
这边只放相对实际的场景用例,给 DOM 元素添加 ref 引用的例子看这里:【模板引用】

<template>
  <el-form :model="formData" ref="formRef">
    <el-form-item label="年龄" prop="age" :rules="formRules">
      <el-input type="age" v-model.number="formData.age"></el-input>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submitForm">提交</el-button>
      <el-button @click="resetForm">重置</el-button>
    </el-form-item>
  </el-form>
</template>
<script>
export default {
  name: 'DemoForm',
  data() {
    return {
      formData: {
        age: '',
      },
      formRules: [
        { required: true, message: '年龄不能为空' },
        { type: 'number', message: '年龄必须为数字值' },
      ],
    }
  },
  methods: {
    submitForm() {
      this.$refs.formRef.validate(valid => {
        if (valid) {
          alert('submit!')
        } else {
          console.log('error submit!!')
          return false
        }
      })
    },
    resetForm() {
      this.$refs.formRef.resetFields()
    },
  },
}
</script>
Vue 3

我们来修改成 Composition API 版本:
别忘了 ref 创建的变量要用.value获取值
详细文档请阅:【在组合式 API 中使用 template refs】

<template>
  <el-form :model="formData" ref="formRef">
    <el-form-item label="年龄" prop="age" :rules="formRules">
      <el-input type="age" v-model.number="formData.age"></el-input>
    </el-form-item>
    <el-form-item>
      <el-button type="primary" @click="submitForm">提交</el-button>
      <el-button @click="resetForm">重置</el-button>
    </el-form-item>
  </el-form>
</template>
<script>
import { reactive, ref } from '@vue/reactivity'
export default {
  setup() {
    const formRef = ref(null) // 1.用 ref 创建一个空引用
    const formData = reactive({
      age: '',
    })
    const formRules = [
      { required: true, message: '年龄不能为空' },
      { type: 'number', message: '年龄必须为数字值' },
    ]
    onMounted(() => {
      // 2.组件实例将在初始渲染后分配给 ref
      console.log(formRef.value)
    })
    const submitForm = () => {
      //  别忘了 ref 创建的变量要用`.value`获取值
      formRef.value.validate(valid => {
        if (valid) {
          alert('submit!')
        } else {
          console.log('error submit!!')
          return false
        }
      })
    }
    const resetForm = () => {
      formRef.value.resetFields()
    }
    return {
      formRef,
      formData,
      formRules,
      submitForm,
      resetForm,
    }
  },
}
</script>

4. 父组件向子组件传值

文档:Props
使用 DOM 模板时,camelCase (驼峰命名法) 的 props 需要使用等价的 kebab-case (短横线分隔命名) 命名。

说明:第 4-8 点 都会结合同一个 Tabs 标签页的例子,循序渐进梳理这几个属性或方法。依然用了element-plus 的组件(TabsTabPane)。

Vue 2

父组件:📃src/views/design/indexV2.vue

// src/views/design/indexV2.vue
<template>
  <div style="padding: 15px">
    <!-- 可以动态赋予一个变量的值 -->
    <!-- 也可以不用`:`传入一个静态的值 -->
    <design-tabs
      :tab-options="tabOptions"
      :active-tab="activeName"
    ></design-tabs>
  </div>
</template>
<script>
import DesignTabs from './DesignTabsV2.vue'
export default {
  name: 'DesignV2',
  components: { DesignTabs },
  data() {
    return {
      tabOptions: [
        { label: '衣服', key: 'clothingDesign', icon: 'cherry' },
        { label: '帽子', key: 'hatDesign', icon: 'cold-drink' },
        { label: '图案', key: 'patternDesign', icon: 'lollipop' },
      ],
      activeName: 'clothingDesign',
    }
  },
}
</script>

子组件 DesignTabs:📃src/views/design/components/DesignTabsV2.vue

// src/views/design/components/DesignTabsV2.vue
<template>
  <el-tabs v-model="activeName">
    <el-tab-pane
      v-for="item in tabOptions"
      :label="item.label"
      :name="item.key"
    >
      <template #label>
        <span>
          <i :class="`el-icon-${item.icon}`" />
          {{ item.label }}
        </span>
      </template>
      <template #default>
        假设这是一个渲染<strong style="color: #409eff">{{ item.label }}</strong
        >列表的子组件
      </template>
    </el-tab-pane>
  </el-tabs>
</template>
<script>
export default {
  name: 'DesignTabsV2',
  // 表示接收的 props
  // 如果定义成对象而不是数组,可以设置 prop 类型、默认值、是否必须
  props: ['tabOptions', 'activeTab'],
  data() {
    return {
      // 如果 prop 用来传递一个初始值
      // 子组件希望将其作为一个本地的数据来使用:
      activeName: this.activeTab,
    }
  }
}
</script>
Vue 3

转化成 Composition API 后的子组件:(一贯省略重复代码)
📃src/views/design/components/DesignTabsV3.vue

// src/views/design/components/DesignTabsV3.vue
<script>
import { ref, toRefs } from 'vue'
export default {
  name: 'DesignTabsV3',
  props: ['tabOptions', 'activeTab'],
  setup(props) {
    const { tabOptions, activeTab } = toRefs(props)
    const activeName = ref(activeTab)
    const handleClick = (tab, event) => {
      console.log(tab, event)
    }
    return {
      tabOptions,
      activeTab,
      activeName,
      handleClick,
    }
  },
}
</script>

5. 计算属性computed

比如我们现在希望在点击 tab 标签的时候能获得 tab 的序号(index):

Vue 2

组件 DesignTabs:📃src/views/design/components/DesignTabsV2.vue

// src/views/design/components/DesignTabsV2.vue
export default {
  name: 'DesignTabsV2',
  computed: {
    tabIndex() {
      return this.tabOptions.findIndex(item => item.key === this.activeName)
    },
  },
  methods: {
    handleClick(tab, event) {
      console.log(this.tabIndex) // tab 对应的 index
      console.log(tab.index, tab.props) 
      // 这样获得的`index`是字符串,props 可以获得传入`tab-pane`的属性
    },
  },
}
Vue 3

这里就比较不一样了,props 的值是不允许在子组件直接修改的。子组件的activeName状态初始值为传入的activeTab,如果activeTabtabOptions一样通过 toRefstoRef 包装,那么activeName的修改是和activeTab深度响应的,这样会报错;
如果要同步修改父组件的值,可以通过emit事件(后面会说)

组件 DesignTabs:📃src/views/design/components/DesignTabsV3.vue

// src/views/design/components/DesignTabsV3.vue
import { ref, toRefs, computed } from 'vue'
export default {
  name: 'DesignTabsV3',
  setup(props) {
    const { tabOptions } = toRefs(props) // 切记 props 只读,不允许在子组件直接修改
    const activeName = ref(props.activeTab) // 将其赋为组件状态的初始值,直接 props.xx
    // const { tabOptions, activeTab } = toRefs(props)
    // const activeName = ref(activeTab) // 这样达咩

    const tabIndex = computed(() =>
      tabOptions.value.findIndex(item => {
        return item.key === activeName.value
      })
    )

    const handleClick = (tab, event) => {
      console.log(tabIndex.value)
    }
    return {
      tabOptions,
      activeName,
      handleClick,
    }
  }
}

这里想额外提一嘴computed可以包在reactive内使用,在组件数据比较简单的情况下甚至可以直接用reactive包起组件全部数据,就好像 Option Api 的 data 选项那样。这样做的目的是在setup()访问不用再带上.value
但这同时会带来一个问题,在模版需要使用state.xx去渲染,而且如果我们 return 的时候把state解构,包含的状态会失去响应性,那就得不偿失了。需要用toRefs包裹再传递,即可维持其响应性。

 // src/views/design/components/DesignTabsV3.vue
import { reactive, computed, toRefs } from 'vue'
export default {
  name: 'DesignTabsV3',
  setup(props) {
    const state = reactive({
      tabOps: props.tabOptions
      activeName: props.activeTab
      tabIndex: computed(() =>
        tabOptions.findIndex(item => {
        return item.key === state.activeName
      })
    })
    return { 
      // ...state //  这样会失去属性的响应式
      ...toRefs(state)
    } 
    // 甚至可以直接这样:
    return toRefs(state)
  }
}

6. 侦听器watch

获取这个tabIndex有什么用呢,目的是在当前标签变化时根据index刷新对应TabPane的子组件的数据。但是如果在标签点击事件触发数据刷新也不合适,因为我们不希望重复点击相同tab时也去刷新。那么这个时候watch或者watchEffect就登场了。

Vue 2

组件 DesignTabs:📃src/views/design/components/DesignTabsV2.vue

// src/views/design/components/DesignTabsV2.vue
<template>
  <!--省略部分 ...-->
      <template #default>
        这是一个渲染<strong style="color: #409eff">{{ item.label }}</strong
        >列表的子组件
        <!--现在我们加上了 DesignList 组件-->
        <design-list :ref="'designList' + i" :type="item.key" />
      </template>
  <!--省略部分...-->
</template>
<script lang="jsx">
import { defineComponent, toRef } from 'vue'

// DesignList 组件,这里图方便混合组合式和选项式 API 写了
const DesignList = defineComponent({
  name: 'DesignList',
  props: ['type'],
  setup(props) {
    const key = toRef(props, 'type')
    return () => <div>{key.value}</div>
  },
  methods: {
    fetchData() {
      console.log(`重新请求${this.type}数据`)
    },  
  },
})

export default {
  name: 'DesignTabsV2',
  watch: {
    activeName(val) {
      console.log(`${val}`)
      // 触发子组件DesignList的数据请求
      this.$refs[`designList${this.tabIndex}`].fetchData() 
    },
  },
}
Vue 3

这个写法可能不是很直观,可以看下 ➡️ 组合式 API 模板引用在 v-for 中的用法
组件 DesignTabs:📃src/views/design/components/DesignTabsV3.vue

// src/views/design/components/DesignTabsV3.vue
<template>
  <!--省略部分 ...-->
  <!--别忘了这是v-for遍历生成的组件-->
    <design-list
      :ref="el => { if (el) designList[i] = el }"
      :type="item.key" :key="item.key + i" 
    />
  <!--省略部分 ...-->
</template>
<script>
import { ref, toRefs, computed, watch, defineComponent, toRef, onBeforeUpdate } from 'vue'

// DesignList 组件同上面代码👆

export default {
  name: 'DesignTabsV3',
  components: { DesignList },
  setup(props) {
    const designList = ref([])
    // 确保在每次更新之前重置ref
    onBeforeUpdate(() => {
      designList.value = []
    })
    watch(activeName, val => {
      console.log(`${val}`)
      designList.value[tabIndex.value].fetchData()
    })
  },
}
</script>

watchwatchEffect 的功能是等效的,都是侦听其依赖,并在依赖值变更时重新运行定义的函数。两者区别:

watch

  • 必须在第一个参数明确指定跟踪的依赖
    侦听器数据源只能是getter/effect函数、refreactive对象,或者包含这些类型(的数据)的数组
    换句话说,只要侦听数据不是refreactive对象,就必须传入一个箭头函数
    打个比方,若要侦听reactive对象的某个属性(例:const state = reactive({ count: 0 })count),便不能像侦听单个ref或整个reactive对象那样直接传一个变量,而是必须在第一个参数传入一个回调函数,如() => state.count
  • 第二个参数是依赖值变更时执行的回调,函数内能访问被侦听状态的当前值和前一个值;
  • 组件初始化时不会执行回调。如果需要可在第三个参数(Object)中设置immediate: true
  • 如果要对多层嵌套状态深度侦听,在第三个参数中设置deep: true

watchEffect

  • 无需手动传入依赖项;
  • 只有一个参数,即侦听数据变更的回调函数,会自动跟踪所有函数中用到的变量;
  • 组件初始化时即会执行一次。

7. 子组件向父组件通信(触发父组件方法)

Vue 2

父组件:📃src/views/design/indexV2.vue

// src/views/design/indexV2.vue
<template>
  <div style="padding: 15px">
    <design-tabs
      :tab-options="tabOptions"
      :active-tab="activeName"
      :tab-change="handleTabChange"
    ></design-tabs>
  </div>
</template>
<script>
export default {
  name: 'DesignV2',
  methods: {
    handleTabChange(tab) {
      this.activeName = tab
    },
  } 
}
</script>

子组件 DesignTabs:📃src/views/design/components/DesignTabsV2.vue

// src/views/design/components/DesignTabsV2.vue
export defalut {
  name: 'DesignTabsV2',
  watch: {
    activeName(val) {
      this.$emit('tabChange', val)
    },
  },
}
Vue 3

子组件 DesignTabs:📃src/views/design/components/DesignTabsV3.vue

// src/views/design/components/DesignTabsV3.vue
export default {
  name: 'DesignTabsV3',
  emits: ['tabChange'],
  setup(props, context) {
    watch(activeName, val => {
      context.emit('tabChange', val)
    })
  }
}

8. 使用 Vue Router

Vue 2

📃src/views/design/components/DesignTabsV2.vue

// src/views/design/components/DesignTabsV2.vue
export defalut {
  name: 'DesignTabsV2',
  watch: {
    activeName(val) {
      this.$router.push(`${this.$route.path}?tab=${val}`)
    },
  },
}
Vue 3

📃src/views/design/components/DesignTabsV3.vue

// src/views/design/components/DesignTabsV3.vue
import { useRouter, useRoute } from 'vue-router'
export default {
  name: 'DesignTabsV3',
  setup() {
    const router = useRouter()
    const route = useRoute()

    watch(activeName, val => {
      router.push(`${route.path}?tab=${val}`)
    })
  },
}

9. 获取 Vuex 对象

Vue 2
export default {
  mounted() {
    console.log(this.$store.state.name)
    this.$store.commit('show')
  } 
}
Vue 3
<script>
import { onMounted } from 'vue'
import { useStore } from 'vuex'
export default {
  setup(props) {
    const store = useStore()

    onMounted(() => {
      console.log(store.name)
      store.commit('show')
    })
  }
}
</script>

参考链接


从 Vue 2 到 Vue 3 组织代码的思维转变(一)- 获取组件实例

python2.7解决UnicodeEncodeError: 'ascii' codec can't encode character问题

最近业务中需要用 Python 写一些脚本。尽管脚本的交互只是命令行 + 日志输出,但是为了让界面友好些,我还是决定用中文输出日志信息。

遇到了异常:

UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)  

为了解决问题,研究了一下 Python 的字符编码处理。网上也有不少文章讲 Python 的字符编码,但是看过一遍,觉得自己可以讲得更明白些。

下面先复述一下 Python 字符串的基础,熟悉此内容的可以跳过。

 1.引入

对应 C/C++ 的 char 和 wchar_t, Python 也有两种字符串类型,str 与 unicode:

# -*- coding: utf-8 -*-  
# file: example1.py  
import string  
  
# 这个是 str 的字符串  
s = '关关雎鸠'  
  
# 这个是 unicode 的字符串  
u = u'关关雎鸠'  
  
print isinstance(s, str)      # True  
print isinstance(u, unicode)  # True  
  
print s.__class__   # <type 'str'>  
print u.__class__   # <type 'unicode'>

前面的申明:# -*- coding: utf-8 -*- 表明,上面的 Python 代码由 utf-8 编码。

为了保证输出不会在 linux 终端上显示乱码,需要设置好 linux 的环境变量:export LANG=en_US.UTF-8

如果你和我一样是使用 SecureCRT,请设置 Session Options/Terminal/Appearance/Character EncodingUTF-8 ,保证能够正确的解码 linux 终端的输出。

两个 Python 字符串类型间可以用 encode / decode 方法转换:

# 从 str 转换成 unicode  
print s.decode('utf-8')   # 关关雎鸠  
  
# 从 unicode 转换成 str  
print u.encode('utf-8')   # 关关雎鸠 

为什么从 unicode 转 str 是 encode,而反过来叫 decode? 

因为 Python 认为 16 位的 unicode 才是字符的唯一内码,而大家常用的字符集如 gb2312,gb18030/gbk,utf-8,以及 ascii 都是字符的二进制(字节)编码形式。把字符从 unicode 转换成二进制编码,当然是要 encode。

反过来,在 Python 中出现的 str 都是用字符集编码的 ansi 字符串。Python 本身并不知道 str 的编码,需要由开发者指定正确的字符集 decode。

(补充一句,其实 Python 是可以知道 str 编码的。因为我们在代码前面申明了 # -*- coding: utf-8 -*-,这表明代码中的 str 都是用 utf-8 编码的,我不知道 Python 为什么不这样做。)

如果用错误的字符集来 encode/decode 会怎样?

# 用 ascii 编码含中文的 unicode 字符串  
u.encode('ascii')  # 错误,因为中文无法用 ascii 字符集编码  
                   # UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)  
  
# 用 gbk 编码含中文的 unicode 字符串  
u.encode('gbk')  # 正确,因为 '关关雎鸠' 可以用中文 gbk 字符集表示  
                 # '\xb9\xd8\xb9\xd8\xf6\xc2\xf0\xaf'  
                 # 直接 print 上面的 str 会显示乱码,修改环境变量为 zh_CN.GBK 可以看到结果是对的  
  
# 用 ascii 解码 utf-8 字符串  
s.decode('ascii')  # 错误,中文 utf-8 字符无法用 ascii 解码  
                   # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)  
  
# 用 gbk 解码 utf-8 字符串  
s.decode('gbk')  # 不出错,但是用 gbk 解码 utf-8 字符流的结果,显然只是乱码  
                 # u'\u934f\u51b2\u53e7\u95c6\u5ea8\u7b2d'

这就遇到了我在本文开头贴出的异常:UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)

现在我们知道了这是个字符串编码异常。接下来, 为什么 Python 这么容易出现字符串编/解码异常? 

这要提到处理 Python 编码时容易遇到的两个陷阱。第一个是有关字符串连接的:

# -*- coding: utf-8 -*-  
# file: example2.py  
  
# 这个是 str 的字符串  
s = '关关雎鸠'  
  
# 这个是 unicode 的字符串  
u = u'关关雎鸠'  
  
s + u  # 失败,UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)  

简单的字符串连接也会出现解码错误?

陷阱一:在进行同时包含 str 与 unicode 的运算时,Python 一律都把 str 转换成 unicode 再运算,当然,运算结果也都是 unicode。

由于 Python 事先并不知道 str 的编码,它只能使用 sys.getdefaultencoding() 编码去 decode。在我的印象里,sys.getdefaultencoding() 的值总是 'ascii' ——显然,如果需要转换的 str 有中文,一定会出现错误。

除了字符串连接,% 运算的结果也是一样的:

# 正确,所有的字符串都是 str, 不需要 decode  
"中文:%s" % s   # 中文:关关雎鸠  
  
# 失败,相当于运行:"中文:%s".decode('ascii') % u  
"中文:%s" % u  # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)  
  
# 正确,所有字符串都是 unicode, 不需要 decode  
u"中文:%s" % u   # 中文:关关雎鸠  
  
# 失败,相当于运行:u"中文:%s" % s.decode('ascii')  
u"中文:%s" % s  # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)  

我不理解为什么 sys.getdefaultencoding() 与环境变量 $LANG 全无关系。如果 Python 用 $LANG 设置 sys.getdefaultencoding() 的值,那么至少开发者遇到 UnicodeDecodeError 的几率会降低 50%。

另外,就像前面说的,我也怀疑为什么 Python 在这里不参考 # -*- coding: utf-8 -*- ,因为 Python 在运行前总是会检查你的代码,这保证了代码里定义的 str 一定是 utf-8 。

对于这个问题,我的唯一建议是在代码里的中文字符串前写上 u。另外,在 Python 3 已经取消了 str,让所有的字符串都是 unicode ——这也许是个正确的决定。

其实,sys.getdefaultencoding() 的值是可以用“后门”方式修改的,我不是特别推荐这个解决方案,但是还是贴一下,因为后面有用:

# -*- coding: utf-8 -*-  
# file: example3.py  
import sys  
  
# 这个是 str 的字符串  
s = '关关雎鸠'  
  
# 这个是 unicode 的字符串  
u = u'关关雎鸠'  
  
# 使得 sys.getdefaultencoding() 的值为 'utf-8'  
reload(sys)                      # reload 才能调用 setdefaultencoding 方法  
sys.setdefaultencoding('utf-8')  # 设置 'utf-8'  
  
# 没问题  
s + u  # u'\u5173\u5173\u96ce\u9e20\u5173\u5173\u96ce\u9e20'  
  
# 同样没问题  
"中文:%s" % u   # u'\u4e2d\u6587\uff1a\u5173\u5173\u96ce\u9e20'  
  
# 还是没问题  
u"中文:%s" % s  # u'\u4e2d\u6587\uff1a\u5173\u5173\u96ce\u9e20'  

可以看到,问题魔术般的解决了。但是注意! sys.setdefaultencoding() 的效果是全局的,如果你的代码由几个不同编码的 Python 文件组成,用这种方法只是按下了葫芦浮起了瓢,让问题变得复杂。

另一个陷阱是有关标准输出的。

刚刚怎么来着?我一直说要设置正确的 linux $LANG 环境变量。那么,设置错误的 $LANG,比如 zh_CN.GBK 会怎样?(避免终端的影响,请把 SecureCRT 也设置成相同的字符集。)

显然会是乱码,但是不是所有输出都是乱码。

# -*- coding: utf-8 -*-  
# file: example4.py  
import string  
  
# 这个是 str 的字符串  
s = '关关雎鸠'  
  
# 这个是 unicode 的字符串  
u = u'关关雎鸠'  
  
# 输出 str 字符串, 显示是乱码  
print s   # 鍏冲叧闆庨笭  
  
# 输出 unicode 字符串,显示正确  
print u  # 关关雎鸠  

为什么是 unicode 而不是 str 的字符显示是正确的? 首先我们需要了解 print。与所有语言一样,这个 Python 命令实际上是把字符打印到标准输出流 —— sys.stdout。而 Python 在这里变了个魔术,它会按照 sys.stdout.encoding 来给 unicode 编码,而把 str 直接输出,扔给操作系统去解决。

这也是为什么要设置 linux $LANG 环境变量与 SecureCRT 一致,否则这些字符会被 SecureCRT 再转换一次,才会交给桌面的 Windows 系统用编码 CP936 或者说 GBK 来显示。

通常情况,sys.stdout.encoding 的值与 linux $LANG 环境变量保持一致:

# -*- coding: utf-8 -*-  
# file: example5.py  
import sys  
  
# 检查标准输出流的编码  
print sys.stdout.encoding  # 设置 $LANG = zh_CN.GBK,  输出 GBK  
                           # 设置 $LANG = en_US.UTF-8,输出 UTF-8  
  
# 这个是 unicode 的字符串  
u = u'关关雎鸠'  
  
# 输出 unicode 字符串,显示正确  
print u  # 关关雎鸠  

但是,这里有 陷阱二:一旦你的 Python 代码是用管道 / 子进程方式运行,sys.stdout.encoding 就会失效,让你重新遇到 UnicodeEncodeError。

比如,用管道方式运行上面的 example4.py 代码:

python -u example5.py | more  
  
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)  
None  

可以看到,第一:sys.stdout.encoding 的值变成了 None;第二:Python 在 print 时会尝试用 ascii 去编码 unicode.

由于 ascii 字符集不能用来表示中文字符,这里当然会编码失败。

怎么解决这个问题? 不知道别人是怎么搞定的,总之我用了一个丑陋的办法:

# -*- coding: utf-8 -*-  
# file: example6.py  
import os  
import sys  
import codecs  
  
# 无论如何,请用 linux 系统的当前字符集输出:  
if sys.stdout.encoding is None:  
    enc = os.environ['LANG'].split('.')[1]  
    sys.stdout = codecs.getwriter(enc)(sys.stdout)  # 替换 sys.stdout  
  
# 这个是 unicode 的字符串  
u = u'关关雎鸠'  
  
# 输出 unicode 字符串,显示正确  
print u  # 关关雎鸠  

  这个方法仍然有个副作用:直接输出中文 str 会失败,因为 codecs 模块的 writer 与 sys.stdout 的行为相反,它会把所有的 str 用 sys.getdefaultencoding() 的字符集转换成 unicode 输出。

# 这个是 str 的字符串  
s = '关关雎鸠'  
  
# 输出 str 字符串, 异常  
print s   # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)  

显然,sys.getdefaultencoding() 的值是 'ascii', 编码失败。

解决办法就像 example3.py 里说的,你要么给 str 加上 u 申明成 unicode,要么通过“后门”去修改 sys.getdefaultencoding():

# 使得 sys.getdefaultencoding() 的值为 'utf-8'  
reload(sys)                      # reload 才能调用 setdefaultencoding 方法  
sys.setdefaultencoding('utf-8')  # 设置 'utf-8'  
  
# 这个是 str 的字符串  
s = '关关雎鸠'  
  
# 输出 str 字符串, OK  
print s   # 关关雎鸠

总而言之,在 Python 2 下进行中文输入输出是个危机四伏的事,特别是在你的代码里混合使用 str 与 unicode 时。

有些模块,例如 json,会直接返回 unicode 类型的字符串,让你的 % 运算需要进行字符解码而失败。而有些会直接返回 str, 你需要知道它们的真实编码,特别是在 print 的时候。

为了避免一些陷阱,上文中说过,最好的办法就是在 Python 代码里永远使用 u 定义中文字符串。另外,如果你的代码需要用管道 / 子进程方式运行,则需要用到 example6.py 里的技巧。

 2.python 自动解编码机制导致报错

1.string 和 unicode 对象合并
>>> s + u''
Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
>>>
2.列表合并
>>> as_list = [u, s]
>>> ''.join(as_list)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
3.格式化字符串
>>> '%s-%s'%(s,u)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
>>>
4.打印 unicode 对象
#test.py
# -*- coding: utf-8 -*-
u = u'中文'
print u
#outpt
Traceback (most recent call last):
  File "/Users/zhyq0826/workspace/zhyq0826/blog-code/p20161030_python_encoding/uni.py", line 3, in <module>
    print u
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
5.输出到文件
>>> f = open('text.txt','w')
>>> f.write(u)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
>>>

1,2,3 的例子中,python 自动用 ascii 把 string 解码为 unicode 对象然后再进行相应操作,所以都是 decode 错误, 4 和 5 python 自动用 ascii 把 unicode 对象编码为字符串然后输出,所以都是 encode 错误。

只要涉及到 unicode 对象和 string 的转换以及 unicode 对象输出、输入的地方可能都会触发 python 自动进行解码/编码,比如写入数据库、写入到文件、读取 socket 等等。

到此,这两个异常产生的真正原因了基本已经清楚了: unicode 对象需要编码为相应的 string(字符串)才可以存储、传输、打印,字符串需要解码为对应的 unicode 对象才能完成 unicode 对象的各种操作,lenfind 等。

string.decode('utf-8') --> unicode
unicode.encode('utf-8') --> string

3.如何避免这些的错误

1.理解编码或解码的转换方向

无论何时发生编码错误,首先要理解编码方向,然后再针对性解决。

2.设置默认编码为 utf-8

在文件头写入

# -*- coding: utf-8 -*-

python 会查找: coding: name or coding=name,并设置文件编码格式为 name,此方式是告诉 python 默认编码不再是 ascii ,而是要使用声明的编码格式。

3.输入对象尽早解码为 unicode,输出对象尽早编码为字节流

无论何时有字节流输入,都需要尽早解码为 unicode 对象。任何时候想要把 unicode 对象写入到文件、数据库、socket 等外界程序,都需要进行编码。

4.使用 codecs 模块来处理输入输出 unicode 对象

codecs 模块可以自动的完成解编码的工作。

>>> import codecs
>>> f = codecs.open('text.txt', 'w', 'utf-8')
>>> f.write(u)
>>> f.close()

参考链接


python2.7 的中文编码处理,解决UnicodeEncodeError: 'ascii' codec can't encode character 问题